From 36128864aaf3040af52dc2fbaaedd3324263ac3c Mon Sep 17 00:00:00 2001 From: JMARyA Date: Wed, 15 Jan 2025 22:46:25 +0100 Subject: [PATCH 1/3] refactor --- Cargo.lock | 40 ++++++------- Cargo.toml | 2 +- src/routes/mod.rs | 43 ++++++++------ src/routes/ui.rs | 141 ++++++++++++++++++++++++++++++--------------- src/routes/user.rs | 19 ++++-- 5 files changed, 153 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 917f0e4..7b57288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "based" version = "0.1.0" -source = "git+https://git.hydrar.de/jmarya/based#a5ecf145a93b96b2545a68ce8e98b44ab50379a2" +source = "git+https://git.hydrar.de/jmarya/based?branch=ui#78e3d6b798eb32d85ab58dd8891457f7ab6f75aa" dependencies = [ "bcrypt", "chrono", @@ -255,9 +255,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "der" @@ -535,7 +535,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -1449,7 +1449,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "libc", "redox_syscall", ] @@ -1488,9 +1488,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "loom" @@ -1562,9 +1562,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -1710,7 +1710,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -2005,7 +2005,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", ] [[package]] @@ -2317,7 +2317,7 @@ version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -2420,7 +2420,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -2695,7 +2695,7 @@ checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.7.0", + "bitflags 2.8.0", "byteorder", "bytes", "chrono", @@ -2739,7 +2739,7 @@ checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.7.0", + "bitflags 2.8.0", "byteorder", "chrono", "crc", @@ -2890,7 +2890,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.8.0", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -3354,9 +3354,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" dependencies = [ "getrandom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 895a6a7..0c35b39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -based = { git = "https://git.hydrar.de/jmarya/based", features = ["htmx"]} +based = { git = "https://git.hydrar.de/jmarya/based", features = ["htmx"], branch = "ui" } bytesize = "1.3.0" chrono = "0.4.39" env_logger = "0.11.6" diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6d4eea2..219e0fd 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,6 +1,7 @@ use based::auth::MaybeUser; -use based::page::{Shell, htmx_link, render_page}; use based::request::{RawResponse, RequestContext, StringResponse, respond_with}; +use based::ui::components::Shell; +use based::ui::{prelude::*, render_page}; use maud::{PreEscaped, html}; use pacco::pkg::mirror::MirrorRepository; use rocket::http::{ContentType, Status}; @@ -24,25 +25,33 @@ pub async fn index_page( let repos: Vec = Repository::list(); let content = html!( - div class="flex justify-between" { - h1 class="text-4xl font-bold pb-6" { "Repositories" }; - @if let Some(user) = user.take_user() { - a class="text-lg" href="/account" { (user.username) }; - }; - }; + (Flex(html! { + h1 class="text-4xl font-bold pb-6" { "Repositories" }; + @if let Some(user) = user.take_user() { + a class="text-lg" href="/account" { (user.username) }; + }; + }).justify(Justify::Between)) + div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" { @for repo in repos { - (htmx_link(&format!("/{repo}"), "flex items-center gap-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md transition hover:bg-gray-200 dark:hover:bg-gray-600", "", html! { - p class="font-medium text-gray-800 dark:text-gray-100" { - (repo) - - @if config.is_mirrored_repo(&repo) { - div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full" { "Mirrored" }; - }; - }; - })) + // TODO : Implement class transition + (Link(&format!("/{repo}"), + Rounded( + Padding( + Shadow::medium( + Flex(Hover( + Background(Gray::_600, Nothing()), + Background(Gray::_700, + Div().vanish().push(Text(&repo).medium().color(&Gray::_100)) + .push(html! { + @if config.is_mirrored_repo(&repo) { + (Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)) + }; + }) + ))).items_center().gap(4))).all(ScreenValue::_4)) + .size(Size::Large))) + } } - }; ); render(content, "Repositories", ctx).await diff --git a/src/routes/ui.rs b/src/routes/ui.rs index 30512b2..81175fc 100644 --- a/src/routes/ui.rs +++ b/src/routes/ui.rs @@ -1,8 +1,6 @@ -use based::{ - page::htmx_link, - request::{RequestContext, StringResponse}, -}; -use maud::{PreEscaped, html}; +use based::request::{RequestContext, StringResponse}; +use based::ui::prelude::*; +use maud::{PreEscaped, Render, html}; use rocket::{State, get}; use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name}; @@ -40,9 +38,10 @@ pub async fn pkg_ui( let content = html! { // Package Name div class="flex flex-wrap gap-2 justify-center items-center" { - (htmx_link(&format!("/{}", repo.name), "text-3xl font-bold text-gray-800 dark:text-gray-100", "", html! { - (repo.name) - })) + (Link(&format!("/{}", repo.name), + Text(&repo.name).bold().color(&Gray::_100)._3xl() + ).use_htmx()) + p class="font-bold p-2 text-gray-400" { "/" }; h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-2" { (pkg.name) @@ -50,18 +49,20 @@ pub async fn pkg_ui( @if pkg.is_signed() { div class="flex items-center gap-2 text-slate-300 pr-4" { - span class="text-2xl font-bold" { - "✓" - } - span class="text-sm font-medium" { "Signed" } - // TODO : Add more info: Who signed? + Public Key recv + (Span("✓")._2xl().bold()) + (Span("Signed").sm().medium()) + // TODO : Add more info: Who signed? + Public Key recv } } @for arch in arch { - span class="px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full" { - (arch.to_string()) - }; + (Rounded( + Padding( + Background(Gray::_700, + Span(&arch.to_string()).medium().sm()) + ).x(ScreenValue::_3).y(ScreenValue::_1) + ).size(Size::Full) + ) } a href=(format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name())) class="ml-4 inline-flex items-center px-2 py-2 bg-gray-200 text-black font-xs rounded-lg shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" { @@ -103,9 +104,11 @@ pub async fn pkg_ui( ul class="space-y-1" { @for version in versions { li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" { - (htmx_link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), if pkg.version.as_ref().map(|x| *x == Package::version(&version).0).unwrap_or_default() { "text-blue-500" } else { "" }, "", html! { - (version) - })) + (Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), + if pkg.version.as_ref() + .map(|x| *x == Package::version(&version).0).unwrap_or_default() + { Text(&version).color(&Blue::_500) } else { Text(&version) } + ).use_htmx()) }; } } @@ -180,6 +183,28 @@ pub async fn pkg_ui( Some(render(content, pkg_name, ctx).await) } +pub fn arch_card(a: &str, repo_name: &str, current: bool) -> PreEscaped { + let url = if current { + &format!("/{repo_name}") + } else { + &format!("/{repo_name}?arch={a}") + }; + + let link = Link(&url, Text(&a).sm().medium().color(&Gray::_300)).use_htmx(); + + Rounded( + Padding(if current { + Background(Blue::_500, link) + } else { + Background(Gray::_700, link) + }) + .x(ScreenValue::_3) + .y(ScreenValue::_1), + ) + .size(Size::Full) + .render() +} + #[get("/?")] pub async fn repo_ui( repo: &str, @@ -212,18 +237,12 @@ pub async fn repo_ui( @for a in architectures { @if let Some(arch) = arch.as_ref() { @if arch.to_string() == a { - (htmx_link(&format!("/{}", repo.name), "px-3 py-1 text-sm font-medium bg-blue-400 dark:bg-blue-500 text-gray-600 dark:text-gray-300 rounded-full", "", html! { - (a) - })); + (arch_card(&a, &repo.name, true)) } @else { - (htmx_link(&format!("/{}?arch={}", repo.name, a), "px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full", "", html! { - (a) - })); + (arch_card(&a, &repo.name, false)) } } @else { - (htmx_link(&format!("/{}?arch={}", repo.name, a), "px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full", "", html! { - (a) - })); + (arch_card(&a, &repo.name, false)) } } } @@ -232,18 +251,41 @@ pub async fn repo_ui( // Package list ul class="space-y-4" { @for pkg in packages { - (htmx_link(&format!("/{}/{pkg}", repo.name), "flex items-center gap-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md transition hover:bg-gray-200 dark:hover:bg-gray-600", "", html! { - div class="w-10 h-10 flex items-center justify-center rounded-full bg-blue-500 text-white font-semibold" { - {(pkg.chars().next().unwrap_or_default().to_uppercase())} - }; - p class="font-medium flex-1 text-gray-800 dark:text-gray-100 max-w-fit" { - (pkg) - }; - div class="flex items-center gap-2 text-slate-300 pr-4" { - span class="text-2xl font-bold" { "✓" }; - span class="text-sm font-medium" { "Signed" }; - }; - })) + ( + Link( + &format!("/{}/{pkg}", repo.name), + // TODO : Implement transition class + Flex( + Padding( + Hover( + Background(Gray::_600, Nothing()), + Background(Gray::_700, + Rounded( + Shadow::medium( + Div().vanish() + .push( + html! { (Sized(ScreenValue::_10, ScreenValue::_10, Rounded( + Background(Blue::_500, Flex(Text( + &pkg.chars().next().unwrap_or_default().to_uppercase().to_string() + ).white().semibold()).full_center().group() + )).size(Size::Full))) } + ) + .push(html! { (Width(ScreenValue::fit, Text(&pkg).medium().color(&Gray::_100))) }) + .push(html! { + ( + Flex( + Padding( + Div().vanish() + .push(Span("✓")._2xl().bold().color(&Slate::_300)) + .push(Span("Signed").sm().medium().color(&Slate::_300)) + ).right(ScreenValue::_4) + ).items_center().gap(2).group() + ) + }) + ) + ).size(Size::Large)))).all(ScreenValue::_4)).items_center().gap(4) + ).use_htmx() + ) } } }; @@ -303,12 +345,15 @@ pub fn build_info(key: String, value: String) -> PreEscaped { } pub fn key_value(key: String, value: String) -> PreEscaped { - html! { - div class="flex items-center" { - span class="font-bold w-32" { (format!("{key}: ")) }; - span class="ml-2" { (value) }; - }; - } + Flex( + Div() + .vanish() + .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) + .push(Margin(Span(&value)).left(ScreenValue::_2)), + ) + .items_center() + .group() + .render() } pub fn take_out(v: &mut Vec, f: impl Fn(&T) -> bool) -> (&mut Vec, Option) { @@ -350,13 +395,13 @@ pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped { }); html! { - div class="flex items-center" { + (Flex(html! { span class="font-bold w-32" { (format!("{key}: ")) }; div class="flex flex-wrap" { @for pkg in pkgs { (pkg) }; }; - }; + }).items_center().group()) } } diff --git a/src/routes/user.rs b/src/routes/user.rs index f3f5b64..03441d5 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -1,7 +1,7 @@ use based::{ auth::{Session, Sessions, User, csrf::CSRF}, - page::htmx_link, request::{RequestContext, StringResponse, api::to_uuid, respond_html}, + ui::AttrExtendable, }; use maud::{PreEscaped, html}; use rocket::{ @@ -14,6 +14,7 @@ use rocket::{ }; use super::render; +use based::ui::prelude::*; #[get("/login")] pub async fn login(ctx: RequestContext) -> StringResponse { @@ -83,7 +84,7 @@ pub async fn new_api_key(user: User, csrf: &str, session_name: &str) -> StringRe if session_name.is_empty() { return respond_html( html! { - div id="next_session" {}; + (Div().id("next_session")) (user.update_csrf().await) } .into_string(), @@ -99,7 +100,7 @@ pub async fn new_api_key(user: User, csrf: &str, session_name: &str) -> StringRe span class="text-red-500" { (api.token) }; }; - div id="next_session" {}; + (Div().id("next_session")) (user.update_csrf().await) } .into_string(), @@ -124,7 +125,14 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse { h2 class="text-xl font-semibold mb-2" { (user.username) }; }; - (htmx_link("/passwd", "mb-6 bg-green-500 text-white py-2 px-6 rounded hover:bg-green-600", "", html! { "Change Password" })) + (Link("/passwd", Margin( + Rounded(Padding( + Hover( + Background(Green::_600, Nothing()), + Background(Green::_500, Text("Change Password").white()) + ) + ).x(ScreenValue::_6).y(ScreenValue::_2)) + ).bottom(ScreenValue::_6)).use_htmx()) section class="mb-6 mt-6" { h3 class="text-lg font-semibold mb-4" { "Active Sessions" }; @@ -134,8 +142,7 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse { (build_session_block(&ses, &user).await) }; - div id="next_session" {}; - + (Div().id("next_session")) } } From 6c1ca1e8c03efdb4fb7fa1eee7f3c40fe8a01a58 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Fri, 17 Jan 2025 17:52:28 +0100 Subject: [PATCH 2/3] finish ui --- Cargo.lock | 10 +- src/routes/mod.rs | 62 ++--- src/routes/ui.rs | 609 ++++++++++++++++++++++++++++----------------- src/routes/user.rs | 2 +- 4 files changed, 421 insertions(+), 262 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b57288..75e8e6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "based" version = "0.1.0" -source = "git+https://git.hydrar.de/jmarya/based?branch=ui#78e3d6b798eb32d85ab58dd8891457f7ab6f75aa" +source = "git+https://git.hydrar.de/jmarya/based?branch=ui#79f08fd202abcfbc52cbab09be7dccc02f4c7c01" dependencies = [ "bcrypt", "chrono", @@ -313,9 +313,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cc" -version = "1.2.9" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -3364,9 +3364,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 219e0fd..825be5b 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,7 +2,7 @@ use based::auth::MaybeUser; use based::request::{RawResponse, RequestContext, StringResponse, respond_with}; use based::ui::components::Shell; use based::ui::{prelude::*, render_page}; -use maud::{PreEscaped, html}; +use maud::{PreEscaped, Render, html}; use pacco::pkg::mirror::MirrorRepository; use rocket::http::{ContentType, Status}; use rocket::{State, get}; @@ -24,35 +24,39 @@ pub async fn index_page( ) -> StringResponse { let repos: Vec = Repository::list(); - let content = html!( - (Flex(html! { - h1 class="text-4xl font-bold pb-6" { "Repositories" }; - @if let Some(user) = user.take_user() { - a class="text-lg" href="/account" { (user.username) }; - }; - }).justify(Justify::Between)) - - div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" { - @for repo in repos { - // TODO : Implement class transition - (Link(&format!("/{repo}"), - Rounded( - Padding( - Shadow::medium( - Flex(Hover( - Background(Gray::_600, Nothing()), - Background(Gray::_700, - Div().vanish().push(Text(&repo).medium().color(&Gray::_100)) - .push(html! { - @if config.is_mirrored_repo(&repo) { - (Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)) - }; - }) - ))).items_center().gap(4))).all(ScreenValue::_4)) - .size(Size::Large))) + let content = Div().vanish() + .push( + Flex( + Div().vanish() + .push(Padding(Text("Repositories")._4xl().bold()).bottom(ScreenValue::_6)) + .push_some(user.take_user().as_ref(), |user: &based::auth::User| Link("/account", Text(&user.username).large())) + ).justify(Justify::Between) + ).push( + html!( + div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" { + @for repo in repos { + (Animated( + Rounded( + Padding( + Shadow::medium( + Flex(Hover( + Background(Gray::_600, Nothing())).on( + Background(Gray::_700, + Link( + &format!("/{repo}"), + Div().vanish().push(Text(&repo).medium().color(&Gray::_100)) + .push_some( + if config.is_mirrored_repo(&repo) { + Some(&()) + } else { None }, + |_: &()| Context(Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)))).use_htmx() + ))).items_center().gap(ScreenValue::_4) + )).all(ScreenValue::_4)) + .size(Size::Large))) + } } - } - ); + ) + ).render(); render(content, "Repositories", ctx).await } diff --git a/src/routes/ui.rs b/src/routes/ui.rs index 81175fc..6659fab 100644 --- a/src/routes/ui.rs +++ b/src/routes/ui.rs @@ -1,5 +1,8 @@ use based::request::{RequestContext, StringResponse}; -use based::ui::prelude::*; +use based::ui::primitives::flex::Strategy; +use based::ui::primitives::space::SpaceBetweenWidget; +use based::ui::primitives::text::{Code, TextWidget}; +use based::ui::{UIWidget, prelude::*}; use maud::{PreEscaped, Render, html}; use rocket::{State, get}; @@ -35,150 +38,212 @@ pub async fn pkg_ui( let binaries = pkg.binaries(); let mut pkginfo = pkg.pkginfo(); - let content = html! { - // Package Name - div class="flex flex-wrap gap-2 justify-center items-center" { - (Link(&format!("/{}", repo.name), + let content = Div().vanish() + .push( + Flex( + // Package Name + Div().vanish() + .push( + Link(&format!("/{}", repo.name), Text(&repo.name).bold().color(&Gray::_100)._3xl() - ).use_htmx()) - - p class="font-bold p-2 text-gray-400" { "/" }; - h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-2" { - (pkg.name) - }; - - @if pkg.is_signed() { - div class="flex items-center gap-2 text-slate-300 pr-4" { - (Span("✓")._2xl().bold()) - (Span("Signed").sm().medium()) + ).use_htmx() + ) + .push(Padding(Text("/").bold().color(&Gray::_400)).all(ScreenValue::_2)) + .push( + Padding(Text(&pkg.name)._3xl().bold().color(&Gray::_100)).right(ScreenValue::_2) + ) + .push_if(pkg.is_signed(), || { + Flex( + Padding( + Div().vanish() + .push(Span("✓")._2xl().bold().color(&Slate::_300)) + .push(Span("Signed").sm().medium().color(&Slate::_300)) // TODO : Add more info: Who signed? + Public Key recv - } - } - - @for arch in arch { - (Rounded( - Padding( - Background(Gray::_700, - Span(&arch.to_string()).medium().sm()) - ).x(ScreenValue::_3).y(ScreenValue::_1) - ).size(Size::Full) - ) - } - - a href=(format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name())) class="ml-4 inline-flex items-center px-2 py-2 bg-gray-200 text-black font-xs rounded-lg shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" { - svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" { - path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; - }; - p class="ml-2" { "Download" }; - }; - - }; - - div class="flex flex-wrap pt-6" { - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition flex-1 m-2 grow" { - h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 underline" { "Info" }; - - @if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1 { - (build_info(desc.0, desc.1)) - } - - @if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "packager" }).1 { - (build_info(desc.0, desc.1)) - } - - @if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "url" }).1 { - (build_info(desc.0, desc.1)) - } - - @if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "size" }).1 { - (build_info(desc.0, desc.1)) - } - - @for (key, val) in pkginfo { - (build_info(key, val)) - }; - }; - - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow md:grow-0 md:shrink" { - h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { "Versions" } - ul class="space-y-1" { - @for version in versions { - li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" { - (Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), - if pkg.version.as_ref() - .map(|x| *x == Package::version(&version).0).unwrap_or_default() - { Text(&version).color(&Blue::_500) } else { Text(&version) } - ).use_htmx()) - }; - } - } - } - }; - - div class="flex flex-wrap pt-6" { - div class="space-y-2" { - h2 class="text-xl font-bold text-gray-700 dark:text-gray-300" { "Content" } - - div class="flex flex-wrap" { - @if !systemd_units.is_empty() { - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { - h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Systemd Units" } - ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { - @for unit in systemd_units { - li { (unit) } - } - } - } - } - - @if !pacman_hooks.is_empty() { - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { - h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Pacman Hooks" } - ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { - @for hook in pacman_hooks { - h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { (hook.0) } - - pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { - (hook.1) - } - } - } - } - } - - @if !binaries.is_empty() { - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { - h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Binaries" } - ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { - @for binary in binaries { - li { (binary) } - } - } - } - } - - div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { - h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Package Files" } - ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { - @for file in pkg.file_list() { - li { (file) } - } - } - } - } - }; - }; - - // Install Script - @if let Some(install_script) = install_script { - div class="space-y-4 pt-6" { - h2 class="text-3xl font-semibold text-gray-700 dark:text-gray-300" { "Install Script" } - pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { - (install_script) - } - }; - } - }; + ).right(ScreenValue::_4) + ).items_center().gap(ScreenValue::_2) + }) + .push_for_each(&arch, |arch: &Architecture| { + Rounded( + Padding( + Background(Gray::_700, + Span(&arch.to_string()).medium().sm()) + ).x(ScreenValue::_3).y(ScreenValue::_1) + ).size(Size::Full) + }) + .push(Margin( + Flex( + Padding( + Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200, + Rounded(Shadow::medium( + Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()), + Paragraph( + html! { + svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" { + path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; + }; + p class="ml-2 text-black" { "Download" }; + } + ).black().xs()))).size(Size::Large) + )) + ).x(ScreenValue::_2).y(ScreenValue::_2) + ).items_center() + ).left(ScreenValue::_4) + ) + ).wrap(Wrap::Wrap).gap(ScreenValue::_2).full_center().group() + ) + .push( + Padding( + Flex( + Div().vanish().push( + Context( + FlexGrow(Strategy::Grow, + Animated(Background(Gray::_800, Shadow::large(Rounded(Margin(Padding(SpaceBetween( + Div() + .push( + Context(Text("Info").xl().semibold().color(&Gray::_300).underlined()) + ) + .push_some( + take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1, + |x: (String, String)| build_info(x.0, x.1) + ) + .push_some( + take_out(&mut pkginfo, |x| { x.0 == "packager" }).1, + |x: (String, String)| build_info(x.0, x.1) + ) + .push_some( + take_out(&mut pkginfo, |x| { x.0 == "url" }).1, + |x: (String, String)| build_info(x.0, x.1) + ) + .push_some( + take_out(&mut pkginfo, |x| { x.0 == "size" }).1, + |x: (String, String)| build_info(x.0, x.1) + ) + .push_for_each(&pkginfo, |(key, val)| build_info(key.clone(), val.clone())) + ).y(ScreenValue::_2)).all(ScreenValue::_4)).all(ScreenValue::_2)).size(Size::Large))))) + )).push( + SpaceBetween( + Padding( + Background(Gray::_800, + Shadow::large( + Rounded( + Margin( + Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on( + FlexGrow(Strategy::Grow, + Div().vanish() + .push( + Text("Versions").xl().semibold().color(&Gray::_300) + ) + .push( + SpaceBetween( + Div().vanish() + .push_for_each(&versions, |version: &String| { + Animated(Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), + if pkg.version.as_ref() + .map(|x| *x == Package::version(&version).0).unwrap_or_default() + { Text(&version).color(&Blue::_500).render() } else { + Hover(Text("").color(&Blue::_400)).on( + Text(&version).color(&Gray::_100)).render() + } + ).use_htmx()) + }) + ).y(ScreenValue::_1) + ) + )) + ).all(ScreenValue::_2) + ).size(Size::Large) + ) + ) + ).all(ScreenValue::_4) + ).y(ScreenValue::_2) + ) + ).wrap(Wrap::Wrap).group() + ).top(ScreenValue::_6) + ).push( + Padding(Flex( + SpaceBetween( + Div().vanish() + .push( + Text("Content").xl().bold().color(&Gray::_300) + ) + .push( + Flex( + Div().vanish() + .push_if(!systemd_units.is_empty(), || { + InfoCard( + Div().vanish() + .push( + CardTitle("Systemd Units") + ) + .push( + ListElements(&systemd_units) + ) + ) + }) + .push_if(!pacman_hooks.is_empty(), || { + InfoCard( + Div().vanish() + .push(CardTitle("Pacman Hooks")) + .push( + Paragraph( + SpaceBetween( + Div().vanish() + .push_for_each(&pacman_hooks, |hook: &(String, String)| { + Div().vanish() + .push( + Text(&hook.0).xl().semibold().color(&Gray::_300) + ) + .push( + Padding(Rounded(Background(Gray::_700, Code(&hook.1).sm().color(&Gray::_100))).size(Size::Large)).all(ScreenValue::_4) + ) + }) + .push( + html! { + @for unit in &systemd_units { + li { (unit) } + } + }) + ).y(ScreenValue::_1) + ).list_style(ListStyle::Disc).color(&Gray::_300) + ) + ) + }) + .push_if(!binaries.is_empty(), + || InfoCard( + Div().vanish() + .push( + CardTitle("Binaries") + ).push( + ListElements(&binaries) + ) + ) + ) + .push( + InfoCard( + Div().vanish().push( + CardTitle("Package Files") + ) + .push( + ListElements(&pkg.file_list()) + ) + ) + ) + ).group().wrap(Wrap::Wrap) + ) + ).y(ScreenValue::_2) + ).wrap(Wrap::Wrap).group()).top(ScreenValue::_6) + ).push_some(install_script.as_ref(), |install_script: &String| { + SpaceBetween( + Margin( + Div() + .push(Text("Install Script")._3xl().semibold().color(&Gray::_300).indentation(ScreenValue::_2)) + .push(Rounded(Padding(Background(Gray::_700, + Code(install_script.trim()) + .color(&Gray::_100).sm() + .indentation(ScreenValue::_0) + )).all(ScreenValue::_2)).size(Size::Large)) + ).top(ScreenValue::_6) + ).y(ScreenValue::_4) + }).render(); Some(render(content, pkg_name, ctx).await) } @@ -222,73 +287,126 @@ pub async fn repo_ui( repo.list_pkg() }; - let content = html! { + let repo_info = Margin( // Repository name and architectures - div class="flex flex-wrap items-center justify-center mb-6" { - h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100" { - (repo.name) - }; + Flex( + Div() + .vanish() + .push(Text(&repo.name)._3xl().bold().color(&Gray::_100)) + .push_if(config.is_mirrored_repo(&repo.name), || { + Background( + Blue::_500, + Rounded( + Margin( + Padding(Text("Mirrored").sm().medium().white()) + .x(ScreenValue::_3) + .y(ScreenValue::_1), + ) + .left(ScreenValue::_4), + ) + .size(Size::Full), + ) + }) + .push( + Screen::medium(Margin(Nothing()).top(ScreenValue::_0)).on(Margin( + Flex(Div().vanish().push_for_each(&architectures, |a: &String| { + html! { + @if let Some(arch) = arch.as_ref() { + @if arch.to_string() == *a { + (arch_card(&a, &repo.name, true)) + } @else { + (arch_card(&a, &repo.name, false)) + } + } @else { + (arch_card(&a, &repo.name, false)) + } + } + })) + .group() + .gap(ScreenValue::_2), + ) + .left(ScreenValue::_4) + .top(ScreenValue::_2)), + ), + ) + .wrap(Wrap::Wrap) + .full_center() + .group(), + ) + .bottom(ScreenValue::_6); - @if config.is_mirrored_repo(&repo.name) { - div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full ml-4" { "Mirrored" }; - }; - - div class="flex gap-2 mt-2 md:mt-0 ml-4" { - @for a in architectures { - @if let Some(arch) = arch.as_ref() { - @if arch.to_string() == a { - (arch_card(&a, &repo.name, true)) - } @else { - (arch_card(&a, &repo.name, false)) - } - } @else { - (arch_card(&a, &repo.name, false)) - } - } - } - }; - - // Package list - ul class="space-y-4" { - @for pkg in packages { - ( - Link( - &format!("/{}/{pkg}", repo.name), - // TODO : Implement transition class - Flex( - Padding( - Hover( - Background(Gray::_600, Nothing()), - Background(Gray::_700, - Rounded( - Shadow::medium( - Div().vanish() - .push( - html! { (Sized(ScreenValue::_10, ScreenValue::_10, Rounded( - Background(Blue::_500, Flex(Text( - &pkg.chars().next().unwrap_or_default().to_uppercase().to_string() - ).white().semibold()).full_center().group() - )).size(Size::Full))) } - ) - .push(html! { (Width(ScreenValue::fit, Text(&pkg).medium().color(&Gray::_100))) }) - .push(html! { - ( - Flex( - Padding( - Div().vanish() - .push(Span("✓")._2xl().bold().color(&Slate::_300)) - .push(Span("Signed").sm().medium().color(&Slate::_300)) - ).right(ScreenValue::_4) - ).items_center().gap(2).group() + let package_list = SpaceBetween(Div().vanish().push_for_each(&packages, |pkg| { + Animated( + Flex( + Padding( + Hover(Background(Gray::_600, Nothing())).on(Background( + Gray::_700, + Rounded(Shadow::medium( + Link( + &format!("/{}/{pkg}", repo.name), + Div() + .vanish() + .push(Context(Sized( + ScreenValue::_10, + ScreenValue::_10, + Rounded(Background( + Blue::_500, + Flex( + Text( + &pkg.chars() + .next() + .unwrap_or_default() + .to_uppercase() + .to_string(), + ) + .white() + .semibold(), ) - }) - ) - ).size(Size::Large)))).all(ScreenValue::_4)).items_center().gap(4) - ).use_htmx() + .full_center() + .group(), + )) + .size(Size::Full), + ))) + .push(Context(Width( + ScreenValue::fit, + Text(&pkg).medium().color(&Gray::_100), + ))) + .push(Context( + Padding( + Flex( + Div() + .vanish() + .push( + Span("✓")._2xl().bold().color(&Slate::_300), + ) + .push( + Span("Signed") + .sm() + .medium() + .color(&Slate::_300), + ), + ) + .items_center() + .gap(ScreenValue::_2) + .group(), + ) + .right(ScreenValue::_4), + )), + ) + .use_htmx(), + )) + .size(Size::Large), + )), + ) + .all(ScreenValue::_4), ) - } - } - }; + .items_center() + .gap(ScreenValue::_4), + ) + })) + .y(ScreenValue::_4); + + let content = Div().vanish().push(repo_info).push(package_list).render(); render(content, &repo.name, ctx).await } @@ -307,12 +425,16 @@ pub fn build_info(key: String, value: String) -> PreEscaped { return key_value("Packager".to_string(), value); } "url" => { - return html! { - div class="flex" { - span class="font-bold w-32" { (format!("Website: ")) }; - a class="ml-2 text-blue-400" href=(value) { (value) }; - }; - }; + return Flex( + Div() + .vanish() + .push(Width(ScreenValue::_32, Span("Website: ").bold())) + .push( + Margin(Link(&value, Span(&value).color(&Blue::_400))).left(ScreenValue::_6), + ), + ) + .group() + .render(); } "builddate" => { let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap(); @@ -349,7 +471,7 @@ pub fn key_value(key: String, value: String) -> PreEscaped { Div() .vanish() .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) - .push(Margin(Span(&value)).left(ScreenValue::_2)), + .push(Margin(Span(&value)).left(ScreenValue::_6)), ) .items_center() .group() @@ -384,24 +506,57 @@ pub fn find_pkg_url(pkg: &str) -> Option { pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped { let pkgs = value.split_whitespace().map(|pkg| { if let Some(pkg_url) = find_pkg_url(pkg) { - html! { - a href=(pkg_url) class="ml-2 text-blue-400" { (pkg) }; - } + Margin(Link(&pkg_url, Text(&pkg).color(&Blue::_400)).use_htmx()).left(ScreenValue::_6) } else { - html! { - span class="ml-2" { (pkg) }; - } + Margin(Span(&pkg)).left(ScreenValue::_6) } }); html! { - (Flex(html! { - span class="font-bold w-32" { (format!("{key}: ")) }; - div class="flex flex-wrap" { - @for pkg in pkgs { - (pkg) - }; - }; - }).items_center().group()) + (Flex( + Div().vanish() + .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold())) + .push(Flex( + html! { + @for pkg in pkgs { + (pkg) + } + } + ).group().wrap(Wrap::Wrap)) + ).items_center().group()) } } + +#[allow(non_snake_case)] +pub fn ListElements(el: &[String]) -> TextWidget { + Paragraph( + SpaceBetween(html! { + @for unit in el { + li { (unit) } + } + }) + .y(ScreenValue::_1), + ) + .list_style(ListStyle::Disc) + .color(&Gray::_300) +} + +#[allow(non_snake_case)] +pub fn CardTitle(title: &str) -> PreEscaped { + Context(Text(title).large().medium().color(&Gray::_100).underlined()) +} + +#[allow(non_snake_case)] +pub fn InfoCard(inner: T) -> SpaceBetweenWidget { + SpaceBetween( + Padding(Background( + Gray::_800, + Shadow::large( + Rounded(Margin(FlexGrow(Strategy::Grow, inner)).all(ScreenValue::_2)) + .size(Size::Large), + ), + )) + .all(ScreenValue::_4), + ) + .y(ScreenValue::_2) +} diff --git a/src/routes/user.rs b/src/routes/user.rs index 03441d5..9a914a1 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -128,7 +128,7 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse { (Link("/passwd", Margin( Rounded(Padding( Hover( - Background(Green::_600, Nothing()), + Background(Green::_600, Nothing())).on( Background(Green::_500, Text("Change Password").white()) ) ).x(ScreenValue::_6).y(ScreenValue::_2)) From 13b0b6095ea6f1164c184ce64bedd7288b7aed31 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 26 Jan 2025 11:08:03 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20Download=20Count=20for=20Packag?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/0001_pkg_meta.sql | 9 +++++++ src/pkg/package.rs | 47 +++++++++++++++++++++++++++++++++--- src/routes/mod.rs | 2 ++ src/routes/ui.rs | 9 +++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 migrations/0001_pkg_meta.sql diff --git a/migrations/0001_pkg_meta.sql b/migrations/0001_pkg_meta.sql new file mode 100644 index 0000000..6dd71c2 --- /dev/null +++ b/migrations/0001_pkg_meta.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS package_meta ( + repo TEXT NOT NULL, + name TEXT NOT NULL, + arch TEXT NOT NULL, + version TEXT NOT NULL, + rel INTEGER NOT NULL, + download_count INTEGER NOT NULL DEFAULT 1, + PRIMARY KEY (repo, name, arch, version, rel) +); diff --git a/src/pkg/package.rs b/src/pkg/package.rs index d275835..ea2a4b4 100644 --- a/src/pkg/package.rs +++ b/src/pkg/package.rs @@ -4,6 +4,8 @@ use std::{ path::{Path, PathBuf}, }; +use based::get_pg; +use sqlx::FromRow; use tar::Archive; use super::{Repository, arch::Architecture}; @@ -17,14 +19,14 @@ pub struct Package { pub arch: Architecture, /// Name of the package pub name: String, - pub rel: u64, + pub rel: i32, /// Version of the package pub version: Option, } impl Package { /// Create a new package - pub fn new(repo: &str, arch: Architecture, pkg_name: &str, version: &str, rel: u64) -> Self { + pub fn new(repo: &str, arch: Architecture, pkg_name: &str, version: &str, rel: i32) -> Self { let pkg = Package { repo: repo.to_string(), arch, @@ -37,7 +39,7 @@ impl Package { pkg } - pub fn version(ver: &str) -> (String, u64) { + pub fn version(ver: &str) -> (String, i32) { let mut splitted = ver.split('-').collect::>(); let rel = splitted.pop().unwrap(); let ver = splitted.join("-"); @@ -402,3 +404,42 @@ pub fn list_tar_file(tar: &PathBuf) -> Option> { Some(paths) } + +#[derive(Debug, Clone, FromRow)] +pub struct PackageMeta { + pub repo: String, + pub name: String, + pub arch: String, + pub version: String, + pub rel: String, + pub download_count: i32, +} + +pub trait PackageMetaInfo { + fn download_amount(&self) -> impl std::future::Future; + fn increase_download_count(&self) -> impl std::future::Future; +} + +impl PackageMetaInfo for Package { + async fn download_amount(&self) -> i32 { + let res: Option<(i32,)> = sqlx::query_as("SELECT download_count FROM package_meta WHERE repo = $1 AND name = $2 AND version = $3 AND arch = $4 AND rel = $5") + .bind(&self.repo) + .bind(&self.name) + .bind(self.version.as_ref().unwrap()) + .bind(&self.arch.to_string()) + .bind(&self.rel) + .fetch_optional(get_pg!()).await.unwrap(); + + res.map(|x| x.0).unwrap_or(0) + } + + async fn increase_download_count(&self) { + sqlx::query("INSERT INTO package_meta (repo, name, arch, version, rel) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (repo, name, arch, version, rel) DO UPDATE SET download_count = package_meta.download_count + 1") + .bind(&self.repo) + .bind(&self.name) + .bind(&self.arch.to_string()) + .bind(&self.version.as_ref().map(|x| x.to_string()).unwrap_or_default()) + .bind(&self.rel) + .execute(get_pg!()).await.unwrap(); + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 825be5b..c47e381 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -4,6 +4,7 @@ use based::ui::components::Shell; use based::ui::{prelude::*, render_page}; use maud::{PreEscaped, Render, html}; use pacco::pkg::mirror::MirrorRepository; +use pacco::pkg::package::PackageMetaInfo; use rocket::http::{ContentType, Status}; use rocket::{State, get}; @@ -98,6 +99,7 @@ pub async fn pkg_route( .unwrap(); if pkg_name.ends_with("pkg.tar.zst") { + pkg.increase_download_count().await; return respond_with( Status::Ok, ContentType::new("application", "tar"), diff --git a/src/routes/ui.rs b/src/routes/ui.rs index 6659fab..6599aba 100644 --- a/src/routes/ui.rs +++ b/src/routes/ui.rs @@ -4,6 +4,7 @@ use based::ui::primitives::space::SpaceBetweenWidget; use based::ui::primitives::text::{Code, TextWidget}; use based::ui::{UIWidget, prelude::*}; use maud::{PreEscaped, Render, html}; +use pacco::pkg::package::PackageMetaInfo; use rocket::{State, get}; use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name}; @@ -30,6 +31,8 @@ pub async fn pkg_ui( pkg.rel = rel; } + let dl_count = pkg.download_amount().await; + let versions = pkg.versions(); let arch = pkg.arch(); let install_script = pkg.install_script(); @@ -118,6 +121,9 @@ pub async fn pkg_ui( take_out(&mut pkginfo, |x| { x.0 == "size" }).1, |x: (String, String)| build_info(x.0, x.1) ) + .push( + build_info("download_amount".to_string(), dl_count.to_string()) + ) .push_for_each(&pkginfo, |(key, val)| build_info(key.clone(), val.clone())) ).y(ScreenValue::_2)).all(ScreenValue::_4)).all(ScreenValue::_2)).size(Size::Large))))) )).push( @@ -413,6 +419,9 @@ pub async fn repo_ui( pub fn build_info(key: String, value: String) -> PreEscaped { match key.as_str() { + "download_amount" => { + return key_value("Downloads".to_string(), value); + } "pkgname" => {} "xdata" => {} "arch" => {}