Skip to content
This repository was archived by the owner on Oct 21, 2025. It is now read-only.

Commit 14acee0

Browse files
committed
redo version stuff
1 parent 16e8dd3 commit 14acee0

File tree

10 files changed

+151
-88
lines changed

10 files changed

+151
-88
lines changed

.sqlx/query-066b90bcf18c93f1784a84486354cfe1b8f212bb65b34866add0336eb84ff300.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-741f78611d94642ed421d0c4c2bbf44496616a140331020d1c57796f4eefad9c.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "api"
3-
version = "2.4.3"
3+
version = "2.5.0"
44
edition = "2024"
55

66
[profile.dev]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "extensions" ADD COLUMN "versions" jsonb NOT NULL DEFAULT '[]'::jsonb;

src/models/extension.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use super::{BaseModel, author::Author};
2-
use indexmap::{IndexMap, IndexSet};
32
use serde::{Deserialize, Serialize};
43
use sqlx::{Row, postgres::PgRow, prelude::Type, types::chrono::NaiveDateTime};
54
use std::collections::BTreeMap;
@@ -16,6 +15,7 @@ pub enum ExtensionType {
1615

1716
#[derive(ToSchema, Serialize, Deserialize)]
1817
pub struct ExtensionVersion {
18+
pub name: String,
1919
pub downloads: u32,
2020

2121
pub created: NaiveDateTime,
@@ -29,10 +29,6 @@ pub struct ExtensionPlatform {
2929

3030
pub reviews: Option<u32>,
3131
pub rating: Option<f64>,
32-
33-
#[schema(inline)]
34-
#[serde(default)]
35-
pub versions: IndexMap<String, ExtensionVersion>,
3632
}
3733

3834
#[derive(ToSchema, Serialize, Deserialize)]
@@ -53,6 +49,9 @@ pub struct Extension {
5349

5450
#[schema(inline)]
5551
pub platforms: BTreeMap<String, ExtensionPlatform>,
52+
#[schema(inline)]
53+
pub versions: Vec<ExtensionVersion>,
54+
5655
pub keywords: Vec<String>,
5756
pub banner: String,
5857

@@ -92,6 +91,10 @@ impl BaseModel for Extension {
9291
format!("{}.platforms", table),
9392
format!("{}platforms", prefix.unwrap_or_default()),
9493
),
94+
(
95+
format!("{}.versions", table),
96+
format!("{}versions", prefix.unwrap_or_default()),
97+
),
9598
(
9699
format!("{}.keywords", table),
97100
format!("{}keywords", prefix.unwrap_or_default()),
@@ -128,6 +131,8 @@ impl BaseModel for Extension {
128131
summary: row.get(format!("{}summary", prefix).as_str()),
129132
platforms: serde_json::from_value(row.get(format!("{}platforms", prefix).as_str()))
130133
.unwrap_or_default(),
134+
versions: serde_json::from_value(row.get(format!("{}versions", prefix).as_str()))
135+
.unwrap_or_default(),
131136
keywords: row.get(format!("{}keywords", prefix).as_str()),
132137
banner: row.get(format!("{}banner", prefix).as_str()),
133138
stats: serde_json::from_value(row.get(format!("{}stats", prefix).as_str())).unwrap(),
@@ -138,11 +143,14 @@ impl BaseModel for Extension {
138143

139144
impl Extension {
140145
#[inline]
141-
pub fn versions(&self) -> IndexSet<&String> {
142-
self.platforms
143-
.values()
144-
.flat_map(|platform| platform.versions.keys())
145-
.collect()
146+
pub fn versions(&self) -> Vec<&String> {
147+
let mut versions: Vec<&String> = Vec::new();
148+
149+
for version in self.versions.iter() {
150+
versions.push(&version.name);
151+
}
152+
153+
versions
146154
}
147155

148156
#[inline]

src/routes/extensions/latest.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use super::State;
2+
use utoipa_axum::{router::OpenApiRouter, routes};
3+
4+
mod index {
5+
use crate::{models::extension::Extension, routes::GetState};
6+
use axum::http::StatusCode;
7+
use indexmap::IndexMap;
8+
9+
#[utoipa::path(get, path = "/", responses(
10+
(status = OK, body = IndexMap<String, String>)
11+
))]
12+
pub async fn route(state: GetState) -> (StatusCode, axum::Json<serde_json::Value>) {
13+
let data = state
14+
.cache
15+
.cached("extensions::all", 300, || async {
16+
Extension::all(&state.database).await
17+
})
18+
.await;
19+
20+
let mut latest_versions: IndexMap<String, String> = IndexMap::new();
21+
for extension in data {
22+
let versions = extension.versions();
23+
24+
if let Some(version) = versions.first() {
25+
latest_versions.insert(extension.identifier.clone(), version.to_string());
26+
}
27+
}
28+
29+
(
30+
StatusCode::OK,
31+
axum::Json(serde_json::to_value(&latest_versions).unwrap()),
32+
)
33+
}
34+
}
35+
36+
pub fn router(state: &State) -> OpenApiRouter<State> {
37+
OpenApiRouter::new()
38+
.routes(routes!(index::route))
39+
.with_state(state.clone())
40+
}

src/routes/extensions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod _extension_;
2+
mod latest;
23

34
use super::State;
45
use utoipa_axum::{router::OpenApiRouter, routes};
@@ -24,6 +25,7 @@ mod index {
2425
pub fn router(state: &State) -> OpenApiRouter<State> {
2526
OpenApiRouter::new()
2627
.nest("/{extension}", _extension_::router(state))
28+
.nest("/latest", latest::router(state))
2729
.routes(routes!(index::route))
2830
.with_state(state.clone())
2931
}

src/routes/latest.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ mod get {
77
use utoipa::ToSchema;
88

99
#[derive(Serialize, ToSchema)]
10-
struct Response {
11-
name: String,
12-
history: Vec<String>,
10+
struct Response<'a> {
11+
name: &'a str,
12+
history: &'a [String],
1313
}
1414

1515
#[utoipa::path(get, path = "/", responses(
1616
(status = OK, body = inline(Response))
1717
))]
1818
pub async fn route(state: GetState) -> axum::Json<serde_json::Value> {
19+
let releases = state.github_releases.read().await;
20+
1921
axum::Json(
2022
serde_json::to_value(Response {
21-
name: state.github_releases.read().await.first().unwrap().clone(),
22-
history: state.github_releases.read().await[1..].to_vec(),
23+
name: releases.first().unwrap(),
24+
history: &releases[1..],
2325
})
2426
.unwrap(),
2527
)

src/schedules/prices.rs

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,30 @@ async fn run_inner(state: State) -> Result<(), Box<dyn std::error::Error>> {
104104
.await
105105
{
106106
Ok(versions) => {
107+
let mut versions: Vec<ExtensionVersion> = versions
108+
.into_iter()
109+
.map(|version| ExtensionVersion {
110+
name: version.name.trim_start_matches("v").to_string(),
111+
downloads: version.downloads_count,
112+
created: chrono::NaiveDateTime::parse_from_str(
113+
&version.created_at,
114+
"%Y-%m-%dT%H:%M:%S%.fZ",
115+
)
116+
.unwrap_or_default(),
117+
})
118+
.collect();
119+
120+
if versions.len() > extension.versions.len() {
121+
versions.sort_unstable_by(|a, b| a.created.cmp(&b.created).reverse());
122+
extension.versions = versions;
123+
}
124+
107125
*key = ExtensionPlatform {
108126
url: key.url.clone(),
109127
price: sxc_product.price,
110128
currency: sxc_product.currency.clone(),
111129
reviews: sxc_product.review_count,
112130
rating: sxc_product.rating_avg,
113-
versions: versions
114-
.into_iter()
115-
.map(|version| {
116-
(
117-
version.name.trim_start_matches("v").to_string(),
118-
ExtensionVersion {
119-
downloads: version.downloads_count,
120-
created: chrono::NaiveDateTime::parse_from_str(
121-
&version.created_at,
122-
"%Y-%m-%dT%H:%M:%S%.fZ",
123-
)
124-
.unwrap_or_default(),
125-
},
126-
)
127-
})
128-
.collect(),
129131
};
130132
}
131133
Err(err) => {
@@ -191,29 +193,33 @@ async fn run_inner(state: State) -> Result<(), Box<dyn std::error::Error>> {
191193
.await
192194
{
193195
Ok(BbbProductVersionResponse { versions }) => {
196+
let mut versions: Vec<ExtensionVersion> = versions
197+
.into_iter()
198+
.map(|version| ExtensionVersion {
199+
name: version.name.trim_start_matches("v").to_string(),
200+
downloads: version.download_count,
201+
created: chrono::DateTime::from_timestamp(
202+
version.release_date,
203+
0,
204+
)
205+
.unwrap_or_default()
206+
.naive_utc(),
207+
})
208+
.collect();
209+
210+
if versions.len() > extension.versions.len() {
211+
versions.sort_unstable_by(|a, b| {
212+
a.created.cmp(&b.created).reverse()
213+
});
214+
extension.versions = versions;
215+
}
216+
194217
*key = ExtensionPlatform {
195218
url: key.url.clone(),
196219
price: product.price,
197220
currency: product.currency.clone(),
198221
reviews: Some(product.review_count),
199222
rating: product.review_average,
200-
versions: versions
201-
.into_iter()
202-
.map(|version| {
203-
(
204-
version.name.trim_start_matches("v").to_string(),
205-
ExtensionVersion {
206-
downloads: version.download_count,
207-
created: chrono::DateTime::from_timestamp(
208-
version.release_date,
209-
0,
210-
)
211-
.unwrap_or_default()
212-
.naive_utc(),
213-
},
214-
)
215-
})
216-
.collect(),
217223
};
218224
}
219225
Err(err) => {
@@ -255,34 +261,36 @@ async fn run_inner(state: State) -> Result<(), Box<dyn std::error::Error>> {
255261
.await
256262
{
257263
Ok(releases) => {
264+
let mut versions: Vec<ExtensionVersion> = releases
265+
.into_iter()
266+
.flat_map(|release| {
267+
release
268+
.assets
269+
.into_iter()
270+
.filter(|asset| asset.name.ends_with(".blueprint"))
271+
.map(move |asset| ExtensionVersion {
272+
name: release.name.trim_start_matches("v").to_string(),
273+
downloads: asset.download_count,
274+
created: chrono::NaiveDateTime::parse_from_str(
275+
&release.published_at,
276+
"%Y-%m-%dT%H:%M:%S%.fZ",
277+
)
278+
.unwrap_or_default(),
279+
})
280+
})
281+
.collect();
282+
283+
if versions.len() > extension.versions.len() {
284+
versions.sort_unstable_by(|a, b| a.created.cmp(&b.created).reverse());
285+
extension.versions = versions;
286+
}
287+
258288
*key = ExtensionPlatform {
259289
url: key.url.clone(),
260290
price: 0.0,
261291
currency: "USD".to_string(),
262292
reviews: Some(0),
263293
rating: None,
264-
versions: releases
265-
.into_iter()
266-
.flat_map(|release| {
267-
release
268-
.assets
269-
.into_iter()
270-
.filter(|asset| asset.name.ends_with(".blueprint"))
271-
.map(move |asset| {
272-
(
273-
release.name.trim_start_matches("v").to_string(),
274-
ExtensionVersion {
275-
downloads: asset.download_count,
276-
created: chrono::NaiveDateTime::parse_from_str(
277-
&release.published_at,
278-
"%Y-%m-%dT%H:%M:%S%.fZ",
279-
)
280-
.unwrap_or_default(),
281-
},
282-
)
283-
})
284-
})
285-
.collect(),
286294
};
287295
}
288296
Err(err) => {
@@ -299,8 +307,9 @@ async fn run_inner(state: State) -> Result<(), Box<dyn std::error::Error>> {
299307
}
300308

301309
sqlx::query!(
302-
"UPDATE extensions SET platforms = $1 WHERE id = $2",
310+
"UPDATE extensions SET platforms = $1, versions = $2 WHERE id = $3",
303311
serde_json::to_value(&extension.platforms)?,
312+
serde_json::to_value(&extension.versions)?,
304313
extension.id
305314
)
306315
.execute(state.database.write())

0 commit comments

Comments
 (0)