Compare commits
No commits in common. "main" and "delete-user" have entirely different histories.
main
...
delete-use
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1 @@
|
||||
/target
|
||||
|
||||
.DS_Store
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
database_path = ""
|
||||
locale = "fr"
|
||||
base_url = ""
|
||||
|
||||
[listener]
|
||||
port = 8000
|
||||
|
||||
312
Cargo.lock
generated
312
Cargo.lock
generated
@ -99,15 +99,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@ -296,12 +287,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base62"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1adf9755786e27479693dedd3271691a92b5e242ab139cacb9fb8e7fb5381111"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@ -337,12 +322,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
@ -381,11 +360,9 @@ dependencies = [
|
||||
"askama_web",
|
||||
"axum",
|
||||
"camino",
|
||||
"csv",
|
||||
"dirs",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"rust-i18n",
|
||||
"sea-orm",
|
||||
"sea-orm-migration",
|
||||
"serde",
|
||||
@ -393,7 +370,7 @@ dependencies = [
|
||||
"snafu",
|
||||
"static-serve",
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"toml",
|
||||
"xdg",
|
||||
]
|
||||
|
||||
@ -420,16 +397,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.1"
|
||||
@ -615,25 +582,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
@ -659,27 +607,6 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
@ -1069,30 +996,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -1399,22 +1302,6 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@ -1475,15 +1362,6 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
@ -1546,7 +1424,7 @@ version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"redox_syscall 0.7.0",
|
||||
]
|
||||
@ -1651,15 +1529,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@ -1915,7 +1784,7 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.10+spec-1.0.0",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2039,7 +1908,7 @@ version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2048,7 +1917,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2169,60 +2038,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
|
||||
dependencies = [
|
||||
"globwalk",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"rust-i18n-macro",
|
||||
"rust-i18n-support",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-macro"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-i18n-support",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-i18n-support"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base62",
|
||||
"globwalk",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"normpath",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"siphasher",
|
||||
"toml 0.8.23",
|
||||
"triomphe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.40.0"
|
||||
@ -2266,15 +2081,6 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.9.0"
|
||||
@ -2330,7 +2136,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"itertools 0.14.0",
|
||||
"itertools",
|
||||
"log",
|
||||
"ouroboros",
|
||||
"pgvector",
|
||||
@ -2534,15 +2340,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.4"
|
||||
@ -2595,19 +2392,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@ -2667,12 +2451,6 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
@ -2835,7 +2613,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@ -2881,7 +2659,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags 2.10.0",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc",
|
||||
@ -3185,18 +2963,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.11+spec-1.1.0"
|
||||
@ -3205,22 +2971,13 @@ checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.4",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
@ -3230,20 +2987,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
@ -3251,7 +2994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
@ -3265,12 +3008,6 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
@ -3352,17 +3089,6 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@ -3408,12 +3134,6 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@ -3461,16 +3181,6 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
|
||||
@ -31,5 +31,3 @@ pretty_env_logger = "0.5.0"
|
||||
# custom logger
|
||||
log = "0.4.29"
|
||||
serde_with = "3.16.1"
|
||||
csv = "1.4.0"
|
||||
rust-i18n = "3.1.5"
|
||||
|
||||
12
assets/css/fork-awesome.min.css
vendored
12
assets/css/fork-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
@ -1,92 +0,0 @@
|
||||
_version: 1
|
||||
name: "BookForge"
|
||||
|
||||
common:
|
||||
download: Download
|
||||
create: Create
|
||||
edit: Edit
|
||||
delete: Delete
|
||||
next: Next
|
||||
previous: Previous
|
||||
actions: Actions
|
||||
search: Search
|
||||
reset: Reset
|
||||
show: Show
|
||||
no_result: No results
|
||||
close: Close
|
||||
confirmation: Confirmation
|
||||
are_you_sure: Are you sure?
|
||||
|
||||
nav:
|
||||
toggle: Toggle navigation
|
||||
books: Books
|
||||
users: Users
|
||||
|
||||
theme:
|
||||
light: Light
|
||||
dark: Dark
|
||||
|
||||
user:
|
||||
attributes:
|
||||
name: Name
|
||||
owner_books: Owned books
|
||||
borrowed_books: Borrowed books
|
||||
|
||||
index:
|
||||
title_tag: Users list | BookForge
|
||||
title: All Users
|
||||
button: Add a user
|
||||
|
||||
edit:
|
||||
title_tag: Edit user | BookForge
|
||||
title: Edit
|
||||
button: Edit user
|
||||
|
||||
new:
|
||||
title_tag: New user | BookForge
|
||||
title: New user
|
||||
button: Create user
|
||||
|
||||
book:
|
||||
attributes:
|
||||
title: Title
|
||||
authors: Author(s)
|
||||
description: Description
|
||||
owner: Owner
|
||||
current_holder: Current holder
|
||||
comment: Comment
|
||||
|
||||
index:
|
||||
title_tag: Books list | BookForge
|
||||
title: All Books
|
||||
|
||||
new:
|
||||
title_tag: New book | BookForge
|
||||
title: New Book
|
||||
button: Create book
|
||||
button_short: Add book
|
||||
|
||||
edit:
|
||||
title_tag: Edit book | BookForge
|
||||
title: Edit book
|
||||
button: Edit book
|
||||
|
||||
show:
|
||||
title_tag: Details | BookForge
|
||||
book_details: Book details
|
||||
user_details: User details
|
||||
more_informations: More information
|
||||
|
||||
footer:
|
||||
message: Made with love & Fuck fascists!
|
||||
|
||||
error:
|
||||
error_404:
|
||||
title_tag: Error 404 | BookForge
|
||||
title: Oops! This page does not exist
|
||||
subtitle: 404 NOT FOUND
|
||||
button: Back to home
|
||||
|
||||
generic:
|
||||
title_tag: Error | BookForge
|
||||
title: Oops! An error occurred
|
||||
@ -1,77 +0,0 @@
|
||||
_version: 1
|
||||
name: "BookForge"
|
||||
common:
|
||||
download: Télécharger
|
||||
create: Créer
|
||||
edit: Modifier
|
||||
delete: Supprimer
|
||||
next: Suivant
|
||||
previous: Précédent
|
||||
actions: Actions
|
||||
search: Filtrer
|
||||
reset: Réini.
|
||||
show: Voir
|
||||
no_result: Aucun résultat
|
||||
close: Fermer
|
||||
confirmation: Confirmation
|
||||
are_you_sure: Êtes-vous sûr ?
|
||||
nav:
|
||||
toggle: Basculer la navigation
|
||||
books: Livres
|
||||
users: Utilisateurs
|
||||
theme:
|
||||
light: Light
|
||||
dark: Dark
|
||||
user:
|
||||
attributes:
|
||||
name: Nom
|
||||
owner_books: Livres possédés
|
||||
borrowed_books: Livres empruntés
|
||||
index:
|
||||
title_tag: Liste des utilisateur.ice.s | BookForge
|
||||
title: Tous les utilisateur.ice.s
|
||||
button: Ajouter un.e utilisateur.ice
|
||||
edit:
|
||||
title_tag: Modifier l'utilisateur.ice | BookForge
|
||||
title: Modifier
|
||||
button: Modifier l'utilisateur.ice
|
||||
new:
|
||||
title_tag: Nouvel utilisateur.ice | BookForge
|
||||
title: Nouvel utilisateur.ice
|
||||
button: Créer l'utilisateur.ice
|
||||
book:
|
||||
attributes:
|
||||
title: Titre
|
||||
authors: Auteur.ice.(s)
|
||||
description: Description
|
||||
owner: Propriétaire
|
||||
current_holder: Détenteur.ice actuel.le
|
||||
comment: Commentaire
|
||||
index:
|
||||
title_tag: Liste des livres | BookForge
|
||||
title: Tous les livres
|
||||
new:
|
||||
title_tag: Nouveau livre | BookForge
|
||||
title: Nouveau livre
|
||||
button: Créer le livre
|
||||
button_short: Ajouter un livre
|
||||
edit:
|
||||
title_tag: Modifier le livre | BookForge
|
||||
title: Modifier le livre
|
||||
button: Modifier le livre
|
||||
show:
|
||||
title_tag: Details | BookForge
|
||||
book_details: Détails du livre
|
||||
user_details: Détails de l'utilisateur.ice
|
||||
more_informations: Plus d'informations
|
||||
footer:
|
||||
message: Fait avec amour & Nique les fachos !
|
||||
error:
|
||||
error_404:
|
||||
title_tag: Erreur 404 | BookForge
|
||||
title: Oups ! Cette page n'existe pas
|
||||
subtitle: 404 NOT FOUND
|
||||
button: Retour à l'accueil
|
||||
generic:
|
||||
title_tag: Erreur | BookForge
|
||||
title: Oups ! Une erreur s'est produite
|
||||
23
src/lib.rs
23
src/lib.rs
@ -2,12 +2,10 @@ use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::{
|
||||
Router,
|
||||
extract::State,
|
||||
routing::{get, post},
|
||||
};
|
||||
use static_serve::embed_assets;
|
||||
|
||||
use crate::routes::router::Router as InternalRouter;
|
||||
use crate::state::AppState;
|
||||
|
||||
mod migrations;
|
||||
@ -15,24 +13,17 @@ mod models;
|
||||
mod routes;
|
||||
pub mod state;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
i18n!("locales", fallback = "en", minify_key = true);
|
||||
|
||||
pub fn build_app(state: AppState) -> Router {
|
||||
rust_i18n::set_locale(&state.config.locale);
|
||||
embed_assets!("assets", compress = true);
|
||||
|
||||
Router::new()
|
||||
.route("/", get(routes::book::index))
|
||||
.route("/books/new", get(routes::book::new))
|
||||
.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/download_csv", get(routes::book::download_csv))
|
||||
.route("/books/new", get(routes::book::new))
|
||||
.route("/users", get(routes::user::index))
|
||||
.route("/users/new", get(routes::user::new))
|
||||
.route("/users/{id}/edit", get(routes::user::edit))
|
||||
@ -46,14 +37,8 @@ pub fn build_app(state: AppState) -> Router {
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "404.html")]
|
||||
struct NotFoundTemplate {
|
||||
pub router: InternalRouter,
|
||||
}
|
||||
struct NotFoundTemplate {}
|
||||
|
||||
pub async fn error_handler(State(state): State<AppState>) -> impl axum::response::IntoResponse {
|
||||
NotFoundTemplate {
|
||||
router: InternalRouter {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
}
|
||||
pub async fn error_handler() -> impl axum::response::IntoResponse {
|
||||
NotFoundTemplate {}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ impl BookOperator {
|
||||
/// Lists all books matching the optional query filters.
|
||||
///
|
||||
/// Results are ordered by ID in descending order (newest first).
|
||||
pub async fn all(&self) -> Result<Vec<Model>, BookError> {
|
||||
pub async fn list(&self) -> Result<Vec<Model>, BookError> {
|
||||
Entity::find()
|
||||
.order_by_desc(Column::Id)
|
||||
.all(&self.state.db)
|
||||
@ -78,18 +78,7 @@ impl BookOperator {
|
||||
.context(DBSnafu)
|
||||
}
|
||||
|
||||
pub async fn all_filtered(&self, query: Option<IndexQuery>) -> Result<Vec<Model>, BookError> {
|
||||
let conditions = Self::filter_conditions(query);
|
||||
|
||||
Entity::find()
|
||||
.filter(conditions)
|
||||
.order_by_desc(Column::Id)
|
||||
.all(&self.state.db)
|
||||
.await
|
||||
.context(DBSnafu)
|
||||
}
|
||||
|
||||
pub async fn all_paginate(
|
||||
pub async fn list_paginate(
|
||||
&self,
|
||||
page: u64,
|
||||
query: Option<IndexQuery>,
|
||||
@ -97,7 +86,24 @@ impl BookOperator {
|
||||
let page = if page > 0 { page } else { 1 }; // keep 1-indexed
|
||||
let page_0indexed = page - 1; // convert for SeaORM (0-based index)
|
||||
|
||||
let conditions = Self::filter_conditions(query);
|
||||
let mut conditions = Condition::all();
|
||||
if let Some(book_query) = query {
|
||||
if let Some(title) = book_query.title {
|
||||
conditions = conditions.add(Column::Title.contains(&title));
|
||||
}
|
||||
|
||||
if let Some(authors) = book_query.authors {
|
||||
conditions = conditions.add(Column::Authors.contains(&authors));
|
||||
}
|
||||
|
||||
if let Some(owner_id) = book_query.owner_id {
|
||||
conditions = conditions.add(Column::OwnerId.eq(owner_id));
|
||||
}
|
||||
|
||||
if let Some(current_holder_id) = book_query.current_holder_id {
|
||||
conditions = conditions.add(Column::CurrentHolderId.eq(current_holder_id));
|
||||
}
|
||||
}
|
||||
|
||||
let book_pages = Entity::find()
|
||||
.filter(conditions)
|
||||
@ -203,28 +209,4 @@ impl BookOperator {
|
||||
|
||||
book.delete(&self.state.db).await.context(DBSnafu)
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
fn filter_conditions(query: Option<IndexQuery>) -> Condition {
|
||||
let mut conditions = Condition::all();
|
||||
if let Some(book_query) = query {
|
||||
if let Some(title) = book_query.title {
|
||||
conditions = conditions.add(Column::Title.contains(&title));
|
||||
}
|
||||
|
||||
if let Some(authors) = book_query.authors {
|
||||
conditions = conditions.add(Column::Authors.contains(&authors));
|
||||
}
|
||||
|
||||
if let Some(owner_id) = book_query.owner_id {
|
||||
conditions = conditions.add(Column::OwnerId.eq(owner_id));
|
||||
}
|
||||
|
||||
if let Some(current_holder_id) = book_query.current_holder_id {
|
||||
conditions = conditions.add(Column::CurrentHolderId.eq(current_holder_id));
|
||||
}
|
||||
}
|
||||
return conditions;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
use crate::models::book;
|
||||
use crate::routes::book::BookForm;
|
||||
use crate::routes::user::IndexQuery;
|
||||
use crate::routes::user::UserForm;
|
||||
use crate::state::AppState;
|
||||
use crate::state::error::UserSnafu;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::Condition;
|
||||
use sea_orm::DeleteResult;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use snafu::ResultExt;
|
||||
@ -55,23 +53,10 @@ impl UserOperator {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub async fn all(&self) -> Result<Vec<Model>, UserError> {
|
||||
pub async fn list(&self) -> Result<Vec<Model>, UserError> {
|
||||
Entity::find().all(&self.state.db).await.context(DBSnafu)
|
||||
}
|
||||
|
||||
pub async fn all_filtered(&self, query: IndexQuery) -> Result<Vec<Model>, UserError> {
|
||||
let mut conditions = Condition::all();
|
||||
if let Some(name) = query.name {
|
||||
conditions = conditions.add(Column::Name.contains(name))
|
||||
}
|
||||
|
||||
Entity::find()
|
||||
.filter(conditions)
|
||||
.all(&self.state.db)
|
||||
.await
|
||||
.context(DBSnafu)
|
||||
}
|
||||
|
||||
pub async fn find_by_id(&self, id: i32) -> Result<Model, UserError> {
|
||||
let user: Option<Model> = Entity::find_by_id(id)
|
||||
.one(&self.state.db)
|
||||
|
||||
@ -4,17 +4,15 @@ use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::{
|
||||
Form,
|
||||
body::Body,
|
||||
extract::{Path, Query, State},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use csv::Writer;
|
||||
use serde::Deserialize;
|
||||
use serde_with::{NoneAsEmptyString, serde_as};
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::{models::book::Model as BookModel, routes::router::Router, state::error::CSVSnafu};
|
||||
use crate::{models::user::Model as UserModel, state::error::IOSnafu};
|
||||
use crate::models::book::Model as BookModel;
|
||||
use crate::models::user::Model as UserModel;
|
||||
|
||||
use crate::{
|
||||
models::{book::BookOperator, user::UserOperator},
|
||||
@ -55,7 +53,6 @@ struct BookIndexTemplate {
|
||||
current_page: u64,
|
||||
total_page: u64,
|
||||
base_query: String,
|
||||
router: Router,
|
||||
}
|
||||
|
||||
pub async fn index(
|
||||
@ -69,13 +66,13 @@ pub async fn index(
|
||||
|
||||
// Get all Users
|
||||
let users = UserOperator::new(state.clone())
|
||||
.all()
|
||||
.list()
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
|
||||
// Get all Book filtered with query
|
||||
let books_paginate = BookOperator::new(state.clone())
|
||||
.all_paginate(page, Some(query.clone()))
|
||||
let books_paginate = BookOperator::new(state)
|
||||
.list_paginate(page, Some(query.clone()))
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
@ -128,9 +125,6 @@ pub async fn index(
|
||||
current_page: books_paginate.current_page,
|
||||
total_page: books_paginate.total_page,
|
||||
base_query,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -140,7 +134,6 @@ struct ShowBookTemplate {
|
||||
book: BookModel,
|
||||
owner: UserModel,
|
||||
current_holder: Option<UserModel>,
|
||||
router: Router,
|
||||
}
|
||||
|
||||
pub async fn show(
|
||||
@ -173,9 +166,6 @@ pub async fn show(
|
||||
book,
|
||||
owner,
|
||||
current_holder,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -208,23 +198,14 @@ pub async fn create(
|
||||
#[template(path = "books/new.html")]
|
||||
struct NewBookTemplate {
|
||||
users: Vec<UserModel>,
|
||||
router: Router,
|
||||
}
|
||||
|
||||
pub async fn new(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
||||
let users = UserOperator::new(state.clone())
|
||||
.all()
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
let users = UserOperator::new(state).list().await.context(UserSnafu)?;
|
||||
|
||||
Ok(NewBookTemplate {
|
||||
users,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
})
|
||||
Ok(NewBookTemplate { users })
|
||||
}
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
@ -232,7 +213,6 @@ pub async fn new(
|
||||
struct EditBookTemplate {
|
||||
users: Vec<UserModel>,
|
||||
book: BookModel,
|
||||
router: Router,
|
||||
}
|
||||
|
||||
pub async fn edit(
|
||||
@ -240,21 +220,15 @@ pub async fn edit(
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
||||
let users = UserOperator::new(state.clone())
|
||||
.all()
|
||||
.list()
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
let book = BookOperator::new(state.clone())
|
||||
let book = BookOperator::new(state)
|
||||
.find_by_id(id)
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
Ok(EditBookTemplate {
|
||||
users,
|
||||
book,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
})
|
||||
Ok(EditBookTemplate { users, book })
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
@ -280,75 +254,3 @@ pub async fn delete(
|
||||
|
||||
Ok(Redirect::to("/").into_response())
|
||||
}
|
||||
|
||||
/// Download CSV filter (no paginate) of all books
|
||||
pub async fn download_csv(
|
||||
State(state): State<AppState>,
|
||||
Query(query): Query<IndexQuery>,
|
||||
) -> Result<impl axum::response::IntoResponse, AppStateError> {
|
||||
let books = BookOperator::new(state.clone())
|
||||
.all_filtered(Some(query))
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
let users = UserOperator::new(state).all().await.context(UserSnafu)?;
|
||||
|
||||
let users_by_id: HashMap<i32, UserModel> = users.into_iter().map(|u| (u.id, u)).collect();
|
||||
|
||||
let mut wtr = Writer::from_writer(vec![]);
|
||||
wtr.write_record(&[
|
||||
"ID",
|
||||
"Title",
|
||||
"Author(s)",
|
||||
"Description",
|
||||
"Owner",
|
||||
"Current Holder",
|
||||
"Comment",
|
||||
])
|
||||
.context(CSVSnafu)?;
|
||||
|
||||
for book in books {
|
||||
let owner_format = match users_by_id.get(&book.owner_id).cloned().ok_or(UserSnafu) {
|
||||
Ok(owner) => format!("{} (id: {})", owner.name.to_string(), owner.id),
|
||||
Err(_) => "-".to_string(),
|
||||
};
|
||||
|
||||
let current_holder = match users_by_id
|
||||
// if current_holder_id is None, take 0.
|
||||
// So get returns errors because user with id 0 can't exist
|
||||
.get(&book.current_holder_id.unwrap_or(0))
|
||||
.cloned()
|
||||
.ok_or(UserSnafu)
|
||||
{
|
||||
Ok(current_holder) => format!(
|
||||
"{} (id: {})",
|
||||
current_holder.name.to_string(),
|
||||
current_holder.id
|
||||
),
|
||||
Err(_) => "-".to_string(),
|
||||
};
|
||||
|
||||
wtr.write_record(&[
|
||||
book.id.to_string(),
|
||||
book.title,
|
||||
book.authors,
|
||||
book.description.unwrap_or_default(),
|
||||
owner_format,
|
||||
current_holder,
|
||||
book.comment.unwrap_or_default(),
|
||||
])
|
||||
.context(CSVSnafu)?;
|
||||
}
|
||||
|
||||
wtr.flush().context(IOSnafu)?;
|
||||
|
||||
let csv_bytes = wtr.into_inner();
|
||||
|
||||
match csv_bytes {
|
||||
Ok(csv_bytes) => Ok(Response::builder()
|
||||
.header("Content-Type", "text/csv")
|
||||
.body(Body::from(csv_bytes))
|
||||
.unwrap()),
|
||||
Err(_) => Ok(Redirect::to("/").into_response()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
pub mod book;
|
||||
pub mod router;
|
||||
pub mod user;
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
#[derive(Clone)]
|
||||
pub struct Router {
|
||||
pub base_path: String,
|
||||
}
|
||||
|
||||
impl Router {
|
||||
pub fn assets(&self, path: &str) -> String {
|
||||
if self.base_path.is_empty() || self.base_path == "/" {
|
||||
format!("/{}", path)
|
||||
} else {
|
||||
format!("{}/{}", self.base_path.trim_end_matches('/'), path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_path(&self) -> String {
|
||||
format!("{}/", &self.base_path)
|
||||
}
|
||||
|
||||
// BOOKS ROUTES
|
||||
|
||||
pub fn new_book_path(&self) -> String {
|
||||
format!("{}/books/new", &self.base_path)
|
||||
}
|
||||
|
||||
pub fn create_book_path(&self) -> String {
|
||||
format!("{}/books", &self.base_path)
|
||||
}
|
||||
|
||||
pub fn update_book_path(&self, id: &i32) -> String {
|
||||
format!("{}/books/{}", &self.base_path, id)
|
||||
}
|
||||
|
||||
pub fn download_csv_book_path(&self) -> String {
|
||||
format!("{}/books/download_csv", &self.base_path)
|
||||
}
|
||||
|
||||
// USERS
|
||||
|
||||
pub fn index_user_path(&self) -> String {
|
||||
format!("{}/users", &self.base_path)
|
||||
}
|
||||
|
||||
pub fn new_user_path(&self) -> String {
|
||||
format!("{}/users/new", &self.base_path)
|
||||
}
|
||||
|
||||
pub fn create_user_path(&self) -> String {
|
||||
format!("{}/users", &self.base_path)
|
||||
}
|
||||
|
||||
pub fn update_user_path(&self, id: &i32) -> String {
|
||||
format!("{}/users/{}", &self.base_path, id)
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,10 @@ use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::{
|
||||
Form,
|
||||
extract::{Path, Query, State},
|
||||
extract::{Path, State},
|
||||
response::Redirect,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_with::{NoneAsEmptyString, serde_as};
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::{
|
||||
@ -16,7 +15,6 @@ use crate::{
|
||||
book::BookOperator,
|
||||
user::{self, UserOperator},
|
||||
},
|
||||
routes::router::Router,
|
||||
state::{
|
||||
AppState,
|
||||
error::{AppStateError, BookSnafu, UserSnafu},
|
||||
@ -26,9 +24,7 @@ use crate::{
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "users/index.html")]
|
||||
struct UsersIndexTemplate {
|
||||
users_with_books_number: Vec<UserWithBookNumber>,
|
||||
query: IndexQuery,
|
||||
router: Router,
|
||||
user_with_books_number: Vec<UserWithBookNumber>,
|
||||
}
|
||||
|
||||
pub struct UserWithBookNumber {
|
||||
@ -40,25 +36,16 @@ pub struct UserWithBookNumber {
|
||||
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())
|
||||
.list()
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
|
||||
let books = BookOperator::new(state.clone())
|
||||
.all()
|
||||
.list()
|
||||
.await
|
||||
.context(BookSnafu)?;
|
||||
|
||||
@ -86,11 +73,7 @@ pub async fn index(
|
||||
}
|
||||
|
||||
Ok(UsersIndexTemplate {
|
||||
users_with_books_number: result,
|
||||
query,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
user_with_books_number: result,
|
||||
})
|
||||
}
|
||||
|
||||
@ -140,36 +123,24 @@ pub async fn delete(
|
||||
#[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())
|
||||
let user = UserOperator::new(state)
|
||||
.find_by_id(id)
|
||||
.await
|
||||
.context(UserSnafu)?;
|
||||
|
||||
Ok(EditTemplate {
|
||||
user,
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
})
|
||||
Ok(EditTemplate { user })
|
||||
}
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "users/new.html")]
|
||||
struct NewTemplate {
|
||||
router: Router,
|
||||
}
|
||||
struct NewTemplate {}
|
||||
|
||||
pub async fn new(State(state): State<AppState>) -> impl axum::response::IntoResponse {
|
||||
NewTemplate {
|
||||
router: Router {
|
||||
base_path: state.config.base_path,
|
||||
},
|
||||
}
|
||||
pub async fn new() -> impl axum::response::IntoResponse {
|
||||
NewTemplate {}
|
||||
}
|
||||
|
||||
@ -32,8 +32,6 @@ pub enum ConfigError {
|
||||
pub struct AppConfig {
|
||||
#[serde(default = "AppConfig::default_sqlite_path")]
|
||||
pub database_path: Utf8PathBuf,
|
||||
pub locale: String,
|
||||
pub base_path: String,
|
||||
pub listener: Listener,
|
||||
}
|
||||
|
||||
@ -41,8 +39,6 @@ impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
AppConfig {
|
||||
database_path: Self::default_sqlite_path(),
|
||||
base_path: Self::default_base_path(),
|
||||
locale: Self::default_locale(),
|
||||
listener: Listener::default(),
|
||||
}
|
||||
}
|
||||
@ -96,14 +92,6 @@ impl AppConfig {
|
||||
Self::config_path().join("BookForge.toml")
|
||||
}
|
||||
|
||||
fn default_locale() -> String {
|
||||
"en".to_string()
|
||||
}
|
||||
|
||||
fn default_base_path() -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
pub fn default_sqlite_path() -> Utf8PathBuf {
|
||||
Self::config_path().join("db.sqlite")
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ use snafu::prelude::*;
|
||||
|
||||
use crate::{
|
||||
models::{book::BookError, user::UserError},
|
||||
routes::router::Router,
|
||||
state::config::ConfigError,
|
||||
};
|
||||
|
||||
@ -34,21 +33,12 @@ pub enum AppStateError {
|
||||
Book {
|
||||
source: BookError,
|
||||
},
|
||||
#[snafu(display("CSV Error"))]
|
||||
CSV {
|
||||
source: csv::Error,
|
||||
},
|
||||
#[snafu(display("IO Error"))]
|
||||
IO {
|
||||
source: std::io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "error.html")]
|
||||
struct ErrorTemplate {
|
||||
state: AppStateErrorContext,
|
||||
router: Router,
|
||||
}
|
||||
|
||||
struct AppStateErrorContext {
|
||||
@ -68,9 +58,6 @@ impl IntoResponse for AppStateError {
|
||||
let error_context = AppStateErrorContext::from(self);
|
||||
ErrorTemplate {
|
||||
state: error_context,
|
||||
router: Router {
|
||||
base_path: "".to_string(),
|
||||
},
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("error.error_404.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="mt-4 text-center">
|
||||
<h1>{{ t!("error.error_404.title") }}</h1>
|
||||
<h2 class="fst-italic">{{ t!("error.error_404.subtitle") }}</h2>
|
||||
<a href="{{ router.root_path() }}" class="mt-3 btn btn-info">{{ t!("error.error_404.button") }}</a>
|
||||
<h1>Oops ! This page does not exist</h1>
|
||||
<h2 class="fst-italic">404 NOT FOUND</h2>
|
||||
<a href="/" class="mt-3 btn btn-info">Back to home</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -2,14 +2,13 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}{{ t!("name") }}{% endblock %}</title>
|
||||
<title>{% block title %}Book Forge{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href='{{ router.assets("assets/css/bootstrap.css") }}'>
|
||||
<link rel="stylesheet" href='{{ router.assets("assets/css/main.css") }}'>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.css">
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
|
||||
<link rel="stylesheet" href='{{ router.assets("assets/css/fork-awesome.min.css") }}'>
|
||||
<link rel="icon" type="image/x-icon" href='{{ router.assets("assets/images/favicon.png") }}'>
|
||||
<link rel="stylesheet" href="/assets/css/fork-awesome.min.css">
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
</head>
|
||||
|
||||
@ -26,7 +25,7 @@
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
||||
<script src='{{ router.assets("assets/js/bootstrap.min.js") }}'></script>
|
||||
<script src='{{ router.assets("assets/js/script.js") }}'></script>
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,27 +2,23 @@
|
||||
{% import "components/typography.html" as typography %}
|
||||
{% import "components/cards.html" as cards %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("book.edit.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ typography::heading(t!("book.edit.title")) }}
|
||||
{{ typography::heading("Editer") }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form method="post" action="{{ router.update_book_path(&book.id) }}">
|
||||
<form method="post" action="/books/{{ book.id }}">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="title">{{ t!("book.attributes.title") }}</label>
|
||||
<label class="form-label" for="title">Name</label>
|
||||
<input type="text" name="title" class="form-control" value="{{ book.title }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="authors" class="form-label">{{ t!("book.attributes.authors") }}</label>
|
||||
<label for="authors" class="form-label">Author(s)</label>
|
||||
<input type="text" name="authors" class="form-control" value="{{ book.authors }}" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="owner_id" class="form-label">{{ t!("book.attributes.owner") }}</label>
|
||||
<label for="owner_id" class="form-label">Owner</label>
|
||||
<select name="owner_id" class="form-control" required>
|
||||
{% for user in users %}
|
||||
{% if book.owner_id == user.id %}
|
||||
@ -35,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="current_holder_id" class="form-label">{{ t!("book.attributes.current_holder") }}</label>
|
||||
<label for="current_holder_id" class="form-label">Current Holder</label>
|
||||
<select class="form-control" name="current_holder_id">
|
||||
<option></option>
|
||||
{% match book.current_holder_id %}
|
||||
@ -56,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">{{ t!("book.attributes.description") }}</label>
|
||||
<label for="description" class="form-label">Description</label>
|
||||
{% match book.description %}
|
||||
{% when Some with (description) %}
|
||||
<textarea name="description" class="form-control">{{ description }}</textarea>
|
||||
@ -66,7 +62,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="comment">{{ t!("book.attributes.comment") }}</label>
|
||||
<label class="form-label" for="comment">Comment</label>
|
||||
{% match book.comment %}
|
||||
{% when Some with (comment) %}
|
||||
<textarea name="comment" class="form-control">{{ comment }}</textarea>
|
||||
@ -76,7 +72,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<input type="submit" value='{{ t!("book.edit.button") }}' class="btn btn-success">
|
||||
<input type="submit" value="Edit book" class="btn btn-success">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
@ -3,32 +3,28 @@
|
||||
{% import "components/cards.html" as cards %}
|
||||
{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("book.new.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ typography::heading(t!("book.new.title")) }}
|
||||
{{ typography::heading("New book") }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form method="post" action="{{ router.create_book_path() }}">
|
||||
{{ form_helpers::input("title", t!("book.attributes.title"), is_required = true, placeholder = "Ex: La Petite Dernière") }}
|
||||
{{ form_helpers::input("authors", t!("book.attributes.authors"), is_required = true, placeholder = "Ex: Fatima Daas") }}
|
||||
<form method="post" action="/books">
|
||||
{{ form_helpers::input("title", "Name", is_required = true, placeholder = "Ex: La Petite Dernière") }}
|
||||
{{ form_helpers::input("authors", "Author(s)", is_required = true, placeholder = "Ex: Fatima Daas") }}
|
||||
|
||||
{% call(option) form_helpers::select("owner_id", t!("book.attributes.owner"), users, is_required = true) %}
|
||||
{% call(option) form_helpers::select("owner_id", "Owner", users, is_required = true) %}
|
||||
<option value="{{ option.id }}">{{ option.name }}</option>
|
||||
{% endcall %}
|
||||
|
||||
{% call(option) form_helpers::select("current_holder_id", t!("book.attributes.current_holder"), users, is_required = false) %}
|
||||
{% call(option) form_helpers::select("current_holder_id", "Current Holder", users, is_required = false) %}
|
||||
<option value="{{ option.id }}">{{ option.name }}</option>
|
||||
{% endcall %}
|
||||
|
||||
{{ form_helpers::textarea("description", "Description", rows = 5, is_required = false, placeholder = "Ex: 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.") }}
|
||||
|
||||
{{ form_helpers::textarea("description", t!("book.attributes.description"), rows = 5, is_required = false, placeholder = "Ex: 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.") }}
|
||||
|
||||
{{ form_helpers::textarea("comment", t!("book.attributes.comment"), rows = 3, is_required = false, placeholder = "Ex: I recommend it, it's great!") }}
|
||||
{{ form_helpers::textarea("comment", "Comment", rows = 3, is_required = false, placeholder = "Ex: I recommend it, it's great!") }}
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<input type="submit" value='{{ t!("book.new.button") }}' class="btn btn-success">
|
||||
<input type="submit" value="Create book" class="btn btn-success">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
@ -4,43 +4,39 @@
|
||||
{% import "components/fields.html" as fields %}
|
||||
{% import "components/cards.html" as cards %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("book.show.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ typography::book_heading(book.title, book, show = false) }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<div class="">
|
||||
<h5 class="fw-bold text-decoration-underline">{{ t!("book.show.book_details") }}</h5>
|
||||
{{ fields::field(t!("book.attributes.title"), book.title) }}
|
||||
{{ fields::field(t!("book.attributes.authors"), book.authors) }}
|
||||
<div class="mt-4">
|
||||
<h5 class="mt-4 fw-bold">Book details</h5>
|
||||
{{ fields::field("Name", book.title) }}
|
||||
{{ fields::field("Authors", book.authors) }}
|
||||
|
||||
{% match book.description %}
|
||||
{% when Some with (description) %}
|
||||
{{ fields::field(t!("book.attributes.description"), description) }}
|
||||
{{ fields::field("Description", description) }}
|
||||
{% when None %}
|
||||
{{ fields::field(t!("book.attributes.description"), "-") }}
|
||||
{{ fields::field("Description", "-") }}
|
||||
{% endmatch %}
|
||||
|
||||
<h5 class="mt-50px fw-bold text-decoration-underline">{{ t!("book.show.user_details") }}</h5>
|
||||
{{ fields::field(t!("book.attributes.owner"), owner.name) }}
|
||||
<h5 class="mt-50px fw-bold">User Details</h5>
|
||||
{{ fields::field("Owner", owner.name) }}
|
||||
|
||||
{% match current_holder %}
|
||||
{% when Some with (current_holder) %}
|
||||
{{ fields::field(t!("book.attributes.current_holder"), current_holder.name) }}
|
||||
{{ fields::field("Current Holder", current_holder.name) }}
|
||||
{% when None %}
|
||||
{{ fields::field(t!("book.attributes.current_holder"), "-") }}
|
||||
{{ fields::field("Current Holder", "-") }}
|
||||
{% endmatch %}
|
||||
|
||||
|
||||
<h5 class="mt-50px fw-bold text-decoration-underline">{{ t!("book.show.more_informations") }}</h5>
|
||||
<h5 class="mt-50px fw-bold">More Informations</h5>
|
||||
{% match book.comment %}
|
||||
{% when Some with (comment) %}
|
||||
{{ fields::field(t!("book.attributes.comment"), comment) }}
|
||||
{{ fields::field("Comment", comment) }}
|
||||
{% when None %}
|
||||
{{ fields::field(t!("book.attributes.comment"), "-") }}
|
||||
{{ fields::field("Comment", "-") }}
|
||||
{% endmatch %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
@ -14,15 +14,15 @@
|
||||
{% macro crud_dropdown_button(book, label, sub_path, show = true) %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ label }}
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if show %}
|
||||
<li><a class="dropdown-item" href="{{ router.root_path() }}{{ sub_path }}/{{ book.id }}">{{ t!("common.show") }}</a></li>
|
||||
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}">Show</a></li>
|
||||
{% endif %}
|
||||
<li><a class="dropdown-item" href="{{ router.root_path() }}{{ sub_path }}/{{ book.id }}/edit">{{ t!("common.edit") }}</a></li>
|
||||
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}/edit">Edit</a></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteUserModal{{ book.id }}">{{ t!("common.delete") }}</a>
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteUserModal{{ book.id }}">Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -32,16 +32,16 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">{{ t!("common.confirmation") }}</h1>
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">Confirmation</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ t!("common.are_you_sure") }}</p>
|
||||
<p>Are you sure ?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t!("common.close") }}</button>
|
||||
<form method="post" action="{{ router.root_path() }}{{ sub_path }}/{{ book.id }}/delete" class="m-0">
|
||||
<input class="btn btn-danger" type="submit" value='{{ t!("common.delete") }}'>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<form method="post" action="/{{ sub_path }}/{{ book.id }}/delete" class="m-0">
|
||||
<input class="btn btn-danger" type="submit" value="Delete">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
{{ dropdown::crud_dropdown_button(book, t!("common.actions"), "books", show) }}
|
||||
{{ dropdown::crud_dropdown_button(book, "Actions", "books", show) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("error.generic.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="mt-4 text-center">
|
||||
<h1>{{ t!("error.generic.title") }}</h1>
|
||||
<h1>Oops ! An Error occured</h1>
|
||||
<div class="alert alert-danger">
|
||||
{% for error in state.errors %}
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="{{ router.root_path() }}" class="mt-3 btn btn-info">{{ t!("error.error_404.button") }}</a>
|
||||
<a href="/" class="mt-3 btn btn-info">Back to home</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -3,22 +3,14 @@
|
||||
{% import "components/dropdown.html" as dropdown %}
|
||||
{% import "components/cards.html" as cards %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("book.index.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% call typography::heading(t!("book.index.title")) %}
|
||||
<a href="{{ router.download_csv_book_path() }}?{{ base_query }}" class="btn btn-info">
|
||||
<i class="fa fa-download me-2" aria-hidden="true"></i> {{ t!("common.download") }} (csv)
|
||||
</a>
|
||||
{% endcall %}
|
||||
{{ typography::heading("All books") }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form method="get">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="title" class="form-label">{{ t!("book.attributes.title") }}</label>
|
||||
<label for="title" class="form-label">Name</label>
|
||||
{% match query.title %}
|
||||
{% when Some with (value) %}
|
||||
<input type="text" name="title" value="{{ value }}" class="form-control" placeholder="Ex: La liberte ou rien">
|
||||
@ -28,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="authors" class="form-label">{{ t!("book.attributes.authors") }}</label>
|
||||
<label for="authors" class="form-label">Authors</label>
|
||||
|
||||
{% match query.authors %}
|
||||
{% when Some with (value) %}
|
||||
@ -39,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="owner_id" class="form-label">{{ t!("book.attributes.owner") }}</label>
|
||||
<label for="owner_id" class="form-label">Owner</label>
|
||||
|
||||
<select name="owner_id" class="form-select">
|
||||
<option></option>
|
||||
@ -61,7 +53,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="current_holder_id" class="form-label">{{ t!("book.attributes.current_holder") }}</label>
|
||||
<label for="current_holder_id" class="form-label">Current Holder</label>
|
||||
|
||||
<select name="current_holder_id" class="form-select">
|
||||
<option></option>
|
||||
@ -83,84 +75,73 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<input type="submit" value='{{ t!("common.search") }}' class="btn btn-info w-100">
|
||||
<input type="submit" value="Search" class="btn btn-info w-100">
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<a href="{{ router.root_path() }}" class="btn btn-light">{{ t!("common.reset") }}</a>
|
||||
<a href="/" class="btn btn-light">Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
{% call cards::card() %}
|
||||
{% if books_with_user.is_empty() %}
|
||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
||||
<h2>{{ t!("common.no_result") }}</h2>
|
||||
|
||||
<a class="btn btn-success text-white text-nowrap mt-3" href="{{ router.new_book_path() }}">
|
||||
{{ t!("book.new.button_short") }}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">{{ t!("book.attributes.title") }}</th>
|
||||
<th scope="col">{{ t!("book.attributes.authors") }}</th>
|
||||
<th scope="col">{{ t!("book.attributes.owner") }}</th>
|
||||
<th scope="col">{{ t!("book.attributes.current_holder") }}</th>
|
||||
<th scope="col">{{ t!("common.actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for book_user in books_with_user %}
|
||||
<tr class="align-middle">
|
||||
<th scope="row">{{ book_user.book.id }}</th>
|
||||
<td>{{ book_user.book.title }}</td>
|
||||
<td>{{ book_user.book.authors }}</td>
|
||||
<td>{{ book_user.owner.name }}</td>
|
||||
<td>
|
||||
{% match book_user.current_holder %}
|
||||
{% when Some with (current_holder) %}
|
||||
{{ current_holder.name }}
|
||||
{% when None %}
|
||||
-
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>
|
||||
{{ dropdown::crud_dropdown_button(book_user.book, t!("common.actions"), "books") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if total_page > 1 %}
|
||||
<div class="d-flex justify-content-center mt-1">
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item {% if current_page <= 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ router.root_path() }}?{{ base_query }}page={% if current_page > 1 %}{{ current_page - 1 }}{% else %}1{% endif %}">{{ t!("common.previous") }}</a>
|
||||
</li>
|
||||
|
||||
{% for page in 1..(total_page + 1) %}
|
||||
{% if page >= current_page - 1 && page <= current_page + 1 %}
|
||||
<li class="page-item {% if page == current_page %}active{% endif %}">
|
||||
<a class="page-link" href="{{ router.root_path() }}?{{ base_query }}page={{ page }}">{{ page }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if current_page == total_page %}disabled{% endif %}">
|
||||
<a class="page-link" href="{{ router.root_path() }}?{{ base_query }}page={{ current_page + 1 }}">{{ t!("common.next") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Author(s)</th>
|
||||
<th scope="col">Owner</th>
|
||||
<th scope="col">Current Holder</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for book_user in books_with_user %}
|
||||
<tr class="align-middle">
|
||||
<th scope="row">{{ book_user.book.id }}</th>
|
||||
<td>{{ book_user.book.title }}</td>
|
||||
<td>{{ book_user.book.authors }}</td>
|
||||
<td>{{ book_user.owner.name }}</td>
|
||||
<td>
|
||||
{% match book_user.current_holder %}
|
||||
{% when Some with (current_holder) %}
|
||||
{{ current_holder.name }}
|
||||
{% when None %}
|
||||
-
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>
|
||||
{{ dropdown::crud_dropdown_button(book_user.book, "Actions", "books") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if total_page > 1 %}
|
||||
<div class="d-flex justify-content-center mt-1">
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item {% if current_page <= 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={% if current_page > 1 %}{{ current_page - 1 }}{% else %}1{% endif %}">Prev</a>
|
||||
</li>
|
||||
|
||||
{% for page in 1..(total_page + 1) %}
|
||||
{% if page >= current_page - 1 && page <= current_page + 1 %}
|
||||
<li class="page-item {% if page == current_page %}active{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={{ page }}">{{ page }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if current_page == total_page %}disabled{% endif %}">
|
||||
<a class="page-link" href="/?{{ base_query }}page={{ current_page + 1 }}">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="container text-center mt-4">
|
||||
<div class="d-flex flex-column flex-sm-row gap-0 gap-sm-3 justify-content-center">
|
||||
<p>{{ t!("footer.message") }}</p>
|
||||
<p>Made with Love & Fuck a Fascist !</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,33 +1,34 @@
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top shadow">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ router.root_path() }}">
|
||||
{{ t!("name") }}
|
||||
<a class="navbar-brand" href="/">
|
||||
BookForge
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="{{ t!("nav.toggle") }}">
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ router.root_path() }}">{{ t!("nav.books") }}</a>
|
||||
<a class="nav-link" href="/">Books</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ router.index_user_path() }}">{{ t!("nav.users") }}</a>
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex align-items-center gap-2 py-3">
|
||||
<select id="changeTheme" class="form-select">
|
||||
<option value="light">{{ t!("theme.light") }}</option>
|
||||
<option value="dark">{{ t!("theme.dark") }}</option>
|
||||
<option value="light">light</option>
|
||||
<option value="dark">dark</option>
|
||||
</select>
|
||||
|
||||
<a class="btn btn-success text-white text-nowrap" href="{{ router.new_book_path() }}">
|
||||
{{ t!("book.new.button_short") }}
|
||||
<a class="btn btn-success text-white" href="/books/new">
|
||||
<i class="fa fa-download me-2"></i>
|
||||
Add book
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
@ -1,24 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "components/typography.html" as typography %}
|
||||
{% import "components/cards.html" as cards %}
|
||||
{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("book.edit.title_tag") }}
|
||||
{% endblock %}
|
||||
o{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block main %}
|
||||
{{ typography::heading(t!("user.edit.title")) }}
|
||||
{{ typography::heading("New User") }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form action="{{ router.update_user_path(&user.id) }}" method="post">
|
||||
<form action="/users/{{ user.id }}" method="post">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-10">
|
||||
{{ form_helpers::input("name", t!("user.attributes.name"), value = user.name, is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
|
||||
{{ form_helpers::input("name", "Name", value = user.name, is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value='{{ t!("user.edit.button") }}' class="btn btn-success">
|
||||
<input type="submit" value="Edit user" class="btn btn-success">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -4,73 +4,35 @@
|
||||
{% import "components/cards.html" as cards %}
|
||||
{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("user.index.title_tag") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% call typography::heading(t!("user.index.title")) %}
|
||||
<a class="btn-success btn" href="{{ router.new_user_path() }}">{{ t!("user.index.button") }}</a>
|
||||
{% call typography::heading("All users") %}
|
||||
<a class="btn-success btn" href="/users/new">Add User</a>
|
||||
{% endcall %}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form method="get">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="name" class="form-label">{{ t!("user.attributes.name") }}</label>
|
||||
{% match query.name %}
|
||||
{% when Some with (value) %}
|
||||
<input type="text" name="name" value="{{ value }}" class="form-control" placeholder="Ex: Koprotkine">
|
||||
{% when None %}
|
||||
<input type="text" name="name" class="form-control" placeholder="Ex: Koprotkine">
|
||||
{% endmatch %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<input type="submit" value='{{ t!("common.search") }}' class="btn btn-info w-100">
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<a href="/users" class="btn btn-light">{{ t!("common.reset") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
{% call cards::card() %}
|
||||
{% if users_with_books_number.is_empty() %}
|
||||
<div class="d-flex flex-column align-items-center justify-content-center">
|
||||
<h2>{{ t!("common.no_result") }}</h2>
|
||||
|
||||
<a class="btn btn-success text-white text-nowrap mt-3" href="{{ router.new_user_path() }}">
|
||||
{{ t!("user.index.button") }}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">{{ t!("user.attributes.name") }}</th>
|
||||
<th scope="col">{{ t!("user.attributes.owner_books") }}</th>
|
||||
<th scope="col">{{ t!("user.attributes.borrowed_books") }}</th>
|
||||
<th scope="col">{{ t!("common.actions") }}</th>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Owner book</th>
|
||||
<th scope="col">Borrowed book</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user_information in user_with_books_number %}
|
||||
<tr class="align-middle">
|
||||
<th scope="row">{{ user_information.user.id }}</th>
|
||||
<td>{{ user_information.user.name }}</td>
|
||||
<td>{{ user_information.owner_book_number }}</td>
|
||||
<td>{{ user_information.borrowed_book_number }}</td>
|
||||
<td>
|
||||
{{ dropdown::crud_dropdown_button(user_information.user, "Actions", "users", show = false) }}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user_information in users_with_books_number %}
|
||||
<tr class="align-middle">
|
||||
<th scope="row">{{ user_information.user.id }}</th>
|
||||
<td>{{ user_information.user.name }}</td>
|
||||
<td>{{ user_information.owner_book_number }}</td>
|
||||
<td>{{ user_information.borrowed_book_number }}</td>
|
||||
<td>
|
||||
{{ dropdown::crud_dropdown_button(user_information.user, t!("common.actions"), "users", show = false) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,23 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "components/typography.html" as typography %}
|
||||
{% import "components/cards.html" as cards %}
|
||||
{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block title %}
|
||||
{{ t!("user.new.title_tag") }}
|
||||
{% endblock %}
|
||||
o{% import "components/inputs.html" as form_helpers %}
|
||||
|
||||
{% block main %}
|
||||
{{ typography::heading(t!("user.new.title")) }}
|
||||
{{ typography::heading("New User") }}
|
||||
|
||||
{% call cards::card() %}
|
||||
<form action="{{ router.create_user_path() }}" method="post">
|
||||
<form action="/users" method="post">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-10">
|
||||
{{ form_helpers::input("name", t!("user.attributes.name"), is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
|
||||
{{ form_helpers::input("name", "Name", is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value='{{ t!("user.new.button") }}' class="btn btn-success">
|
||||
<input type="submit" value="Create user" class="btn btn-success">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user