Skip to content

Commit

Permalink
Implement badges/latest-versions
Browse files Browse the repository at this point in the history
  • Loading branch information
AMDmi3 committed Sep 12, 2024
1 parent cef6ed7 commit 5f788bc
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions repology-webapp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ttf-parser = "0.24.1"
strum_macros = "0.26.4"
strum = { version = "0.26.3", features = ["strum_macros", "derive"] }
chrono = { version = "0.4.38", default-features = false, features = ["std", "now"] }
itertools = "0.13.0"

[dev-dependencies]
repology-webapp-test-utils = { path = "../repology-webapp-test-utils" }
4 changes: 4 additions & 0 deletions repology-webapp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ pub async fn create_app(pool: PgPool) -> Result<Router, Error> {
"/badge/vertical-allrepos/:project_name.svg",
get(views::badge_vertical_allrepos),
)
.route(
"/badge/latest-versions/:project_name.svg",
get(views::badge_latest_versions),
)
.with_state(state))
}
2 changes: 2 additions & 0 deletions repology-webapp/src/views/badges.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2024 Dmitry Marakasov <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

pub mod latest_versions;
pub mod tiny_repos;
pub mod version_for_repo;
pub mod vertical_allrepos;

pub use latest_versions::*;
pub use tiny_repos::*;
pub use version_for_repo::*;
pub use vertical_allrepos::*;
123 changes: 123 additions & 0 deletions repology-webapp/src/views/badges/latest_versions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: Copyright 2024 Dmitry Marakasov <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

use std::borrow::Cow;

use axum::extract::{Path, Query, State};
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::IntoResponse;
use itertools::Itertools;
use serde::Deserialize;
use sqlx::FromRow;

use libversion::AsVersionWithFlags;

use repology_common::{PackageFlags, PackageStatus};

use crate::badges::{render_generic_badge, Cell};
use crate::package::traits::{PackageWithFlags, PackageWithStatus, PackageWithVersion};
use crate::package::version::package_version;
use crate::result::EndpointResult;
use crate::state::AppState;

#[derive(Deserialize)]
pub struct QueryParams {
#[serde(rename = "header")]
pub caption: Option<String>,
}

#[derive(FromRow)]
pub struct Package {
pub version: String,
pub status: PackageStatus,
pub flags: i32,
}

impl PackageWithVersion for Package {
fn version(&self) -> &str {
&self.version
}
}
impl PackageWithFlags for Package {
fn flags(&self) -> PackageFlags {
PackageFlags::from_bits(self.flags as u32).expect("flags must be deserializable")
}
}
impl PackageWithStatus for Package {
fn status(&self) -> PackageStatus {
self.status
}
}

pub async fn badge_latest_versions(
Path(project_name): Path<String>,
State(state): State<AppState>,
Query(query): Query<QueryParams>,
) -> EndpointResult {
let project_name = if let Some(project_name) = project_name.strip_suffix(".svg") {
project_name
} else {
return Ok((StatusCode::NOT_FOUND, "path must end with .svg".to_owned()).into_response());
};

let packages: Vec<Package> = sqlx::query_as(
r#"
SELECT
version,
versionclass AS status,
flags
FROM packages
WHERE effname = $1 AND versionclass IN (1, 4, 5) -- Newest, Unique, Devel
"#,
)
.bind(project_name)
.fetch_all(&state.pool)
.await?;

let versions = packages
.iter()
.map(|package| package_version(package))
.sorted_by(|a, b| {
// version desc → version string length desc → version string lexographical
a.cmp(b)
.then_with(|| a.version().len().cmp(&b.version().len()))
.reverse()
.then_with(|| a.version().cmp(&b.version()))
})
.dedup_by(|a, b| a.version() == b.version())
.collect::<Vec<_>>();

let (default_caption, text) = match versions.len() {
0 => ("latest packaged version", Cow::from("-")),
1 => ("latest packaged version", Cow::from(versions[0].version())),
_ => (
"latest packaged versions",
Cow::from(versions.iter().map(|version| version.version()).join(", ")),
),
};

let caption_cell = Cell::new(
query
.caption
.as_ref()
.map_or(default_caption, String::as_str),
)
.collapsible(true);
let version_cell = Cell::new(&text).color("#007ec6");

let body = render_generic_badge(
&[vec![caption_cell, version_cell]],
None,
0,
&state.font_measurer,
)?;

Ok((
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::IMAGE_SVG.as_ref()),
)],
body,
)
.into_response())
}
34 changes: 34 additions & 0 deletions repology-webapp/tests/badges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,37 @@ async fn test_badge_vertical_allrepos(pool: PgPool) {
@"string(//svg:g[1]/svg:g[@font-size=11][1]/svg:text[15])" == "1.0",
);
}

#[sqlx::test(
migrator = "repology_common::MIGRATOR",
fixtures("badge_versions_data")
)]
async fn test_badge_latest_versions(pool: PgPool) {
check_code!(pool, "/badge/latest-versions/nonexistent", NOT_FOUND);
check_svg!(
pool,
"/badge/latest-versions/nonexistent.svg",
@"count(//svg:g[1]/svg:g[1]/svg:text)" == 4_f64,
@"string(//svg:g[1]/svg:g[1]/svg:text[1])" == "latest packaged version",
@"string(//svg:g[1]/svg:g[1]/svg:text[3])" == "-",
);
check_svg!(
pool,
"/badge/latest-versions/zsh.svg",
@"count(//svg:g[1]/svg:g[1]/svg:text)" == 4_f64,
@"string(//svg:g[1]/svg:g[1]/svg:text[1])" == "latest packaged versions",
@"string(//svg:g[1]/svg:g[1]/svg:text[3])" == "3.0, 1.0.0, 1_0_0, 1.0",
);

// caption flags
check_svg!(
pool,
"/badge/latest-versions/zsh.svg?header=VERSIONS",
@"string(//svg:g[1]/svg:g[1]/svg:text[1])" == "VERSIONS",
);
check_svg!(
pool,
"/badge/latest-versions/zsh.svg?header=",
@"count(//svg:g[1]/svg:g[1]/svg:text)" == 2_f64,
);
}
7 changes: 7 additions & 0 deletions repology-webapp/tests/fixtures/badge_versions_data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
INSERT INTO packages(effname, version, versionclass, flags, repo, family, trackname, visiblename, projectname_seed, origversion, rawversion, shadow) VALUES
('zsh', '3.0', 5, 0, '', '', '', '', '', '', '', false),
('zsh', '1_0_0', 1, 0, '', '', '', '', '', '', '', false),
('zsh', '1.0.0', 1, 0, '', '', '', '', '', '', '', false),
('zsh', '1.0', 1, 0, '', '', '', '', '', '', '', false),
('zsh', '2024', 2, 1 << 8, '', '', '', '', '', '', '', false), -- sink
('zsh', '2025', 3, 0, '', '', '', '', '', '', '', false); -- ignored

0 comments on commit 5f788bc

Please sign in to comment.