You've already forked AstralRinth
forked from didirus/AstralRinth
Version ordering [MOD-551] (#740)
* Version ordering * cargo sqlx prepare * Use version ordering for maven * Use version ordering when sorting versions in Rust (not just SQL) * Thanks clippy
This commit is contained in:
@@ -28,6 +28,7 @@ pub struct VersionBuilder {
|
||||
pub featured: bool,
|
||||
pub status: VersionStatus,
|
||||
pub requested_status: Option<VersionStatus>,
|
||||
pub ordering: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -214,6 +215,7 @@ impl VersionBuilder {
|
||||
version_type: self.version_type,
|
||||
status: self.status,
|
||||
requested_status: self.requested_status,
|
||||
ordering: self.ordering,
|
||||
};
|
||||
|
||||
version.insert(transaction).await?;
|
||||
@@ -317,7 +319,7 @@ impl VersionVersion {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Version {
|
||||
pub id: VersionId,
|
||||
pub project_id: ProjectId,
|
||||
@@ -332,6 +334,7 @@ pub struct Version {
|
||||
pub featured: bool,
|
||||
pub status: VersionStatus,
|
||||
pub requested_status: Option<VersionStatus>,
|
||||
pub ordering: Option<i32>,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
@@ -344,12 +347,12 @@ impl Version {
|
||||
INSERT INTO versions (
|
||||
id, mod_id, author_id, name, version_number,
|
||||
changelog, date_published, downloads,
|
||||
version_type, featured, status
|
||||
version_type, featured, status, ordering
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6, $7, $8,
|
||||
$9, $10, $11
|
||||
$9, $10, $11, $12
|
||||
)
|
||||
",
|
||||
self.id as VersionId,
|
||||
@@ -362,7 +365,8 @@ impl Version {
|
||||
self.downloads,
|
||||
&self.version_type,
|
||||
self.featured,
|
||||
self.status.as_str()
|
||||
self.status.as_str(),
|
||||
self.ordering
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
@@ -554,7 +558,7 @@ impl Version {
|
||||
"
|
||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||
v.changelog changelog, v.date_published date_published, v.downloads downloads,
|
||||
v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,
|
||||
v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering,
|
||||
JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,
|
||||
ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||
JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files,
|
||||
@@ -570,7 +574,7 @@ impl Version {
|
||||
LEFT OUTER JOIN dependencies d on v.id = d.dependent_id
|
||||
WHERE v.id = ANY($1)
|
||||
GROUP BY v.id
|
||||
ORDER BY v.date_published ASC;
|
||||
ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC;
|
||||
",
|
||||
&version_ids_parsed
|
||||
)
|
||||
@@ -593,6 +597,7 @@ impl Version {
|
||||
status: VersionStatus::from_string(&v.status),
|
||||
requested_status: v.requested_status
|
||||
.map(|x| VersionStatus::from_string(&x)),
|
||||
ordering: v.ordering,
|
||||
},
|
||||
files: {
|
||||
#[derive(Deserialize)]
|
||||
@@ -851,7 +856,7 @@ impl Version {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct QueryVersion {
|
||||
pub inner: Version,
|
||||
|
||||
@@ -861,7 +866,7 @@ pub struct QueryVersion {
|
||||
pub dependencies: Vec<QueryDependency>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct QueryDependency {
|
||||
pub project_id: Option<ProjectId>,
|
||||
pub version_id: Option<VersionId>,
|
||||
@@ -869,7 +874,7 @@ pub struct QueryDependency {
|
||||
pub dependency_type: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct QueryFile {
|
||||
pub id: FileId,
|
||||
pub url: String,
|
||||
@@ -892,3 +897,84 @@ pub struct SingleFile {
|
||||
pub size: u32,
|
||||
pub file_type: Option<FileType>,
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for QueryVersion {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.inner.cmp(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for QueryVersion {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let ordering_order = match (self.ordering, other.ordering) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(Some(a), Some(b)) => a.cmp(&b),
|
||||
};
|
||||
|
||||
match ordering_order {
|
||||
Ordering::Equal => self.date_published.cmp(&other.date_published),
|
||||
ordering => ordering,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::Months;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_version_sorting() {
|
||||
let versions = vec![
|
||||
get_version(4, None, months_ago(6)),
|
||||
get_version(3, None, months_ago(7)),
|
||||
get_version(2, Some(1), months_ago(6)),
|
||||
get_version(1, Some(0), months_ago(4)),
|
||||
get_version(0, Some(0), months_ago(5)),
|
||||
];
|
||||
|
||||
let sorted = versions.iter().cloned().sorted().collect_vec();
|
||||
|
||||
let expected_sorted_ids = vec![0, 1, 2, 3, 4];
|
||||
let actual_sorted_ids = sorted.iter().map(|v| v.id.0).collect_vec();
|
||||
assert_eq!(expected_sorted_ids, actual_sorted_ids);
|
||||
}
|
||||
|
||||
fn months_ago(months: u32) -> DateTime<Utc> {
|
||||
Utc::now().checked_sub_months(Months::new(months)).unwrap()
|
||||
}
|
||||
|
||||
fn get_version(id: i64, ordering: Option<i32>, date_published: DateTime<Utc>) -> Version {
|
||||
Version {
|
||||
id: VersionId(id),
|
||||
ordering,
|
||||
date_published,
|
||||
project_id: ProjectId(0),
|
||||
author_id: UserId(0),
|
||||
name: Default::default(),
|
||||
version_number: Default::default(),
|
||||
changelog: Default::default(),
|
||||
changelog_url: Default::default(),
|
||||
downloads: Default::default(),
|
||||
version_type: Default::default(),
|
||||
featured: Default::default(),
|
||||
status: VersionStatus::Listed,
|
||||
requested_status: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +491,8 @@ pub struct Version {
|
||||
pub game_versions: Vec<GameVersion>,
|
||||
/// The loaders that this version works on
|
||||
pub loaders: Vec<Loader>,
|
||||
/// Ordering override, lower is returned first
|
||||
pub ordering: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<QueryVersion> for Version {
|
||||
@@ -515,6 +517,7 @@ impl From<QueryVersion> for Version {
|
||||
"alpha" => VersionType::Alpha,
|
||||
_ => VersionType::Release,
|
||||
},
|
||||
ordering: v.ordering,
|
||||
|
||||
status: v.status,
|
||||
requested_status: v.requested_status,
|
||||
@@ -729,7 +732,7 @@ impl DependencyType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum FileType {
|
||||
RequiredResourcePack,
|
||||
|
||||
@@ -100,7 +100,7 @@ pub async fn maven_metadata(
|
||||
SELECT id, version_number, version_type
|
||||
FROM versions
|
||||
WHERE mod_id = $1 AND status = ANY($2)
|
||||
ORDER BY date_published ASC
|
||||
ORDER BY ordering ASC NULLS LAST, date_published ASC
|
||||
",
|
||||
project.inner.id as database::models::ids::ProjectId,
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
|
||||
@@ -964,6 +964,7 @@ async fn create_initial_version(
|
||||
status: VersionStatus::Listed,
|
||||
version_type: version_data.release_channel.to_string(),
|
||||
requested_status: None,
|
||||
ordering: version_data.ordering,
|
||||
};
|
||||
|
||||
Ok(version)
|
||||
|
||||
@@ -76,6 +76,9 @@ pub struct InitialVersionData {
|
||||
#[validate(length(max = 10))]
|
||||
#[serde(default)]
|
||||
pub uploaded_images: Vec<ImageId>,
|
||||
|
||||
// The ordering relative to other versions
|
||||
pub ordering: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -316,6 +319,7 @@ async fn version_create_inner(
|
||||
featured: version_create_data.featured,
|
||||
status: version_create_data.status,
|
||||
requested_status: None,
|
||||
ordering: version_create_data.ordering,
|
||||
});
|
||||
|
||||
return Ok(());
|
||||
@@ -427,6 +431,7 @@ async fn version_create_inner(
|
||||
version_type: version_data.release_channel,
|
||||
status: builder.status,
|
||||
requested_status: builder.requested_status,
|
||||
ordering: builder.ordering,
|
||||
files: builder
|
||||
.files
|
||||
.iter()
|
||||
|
||||
@@ -323,7 +323,7 @@ pub async fn get_update_from_hash(
|
||||
|
||||
bool
|
||||
})
|
||||
.sorted_by(|a, b| a.inner.date_published.cmp(&b.inner.date_published))
|
||||
.sorted()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(first) = versions.pop() {
|
||||
@@ -522,7 +522,7 @@ pub async fn update_files(
|
||||
|
||||
bool
|
||||
})
|
||||
.sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published))
|
||||
.sorted()
|
||||
.next();
|
||||
|
||||
if let Some(version) = version {
|
||||
@@ -629,7 +629,7 @@ pub async fn update_individual_files(
|
||||
|
||||
bool
|
||||
})
|
||||
.sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published))
|
||||
.sorted()
|
||||
.next();
|
||||
|
||||
if let Some(version) = version {
|
||||
|
||||
@@ -115,7 +115,7 @@ pub async fn version_list(
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||
versions.sort();
|
||||
|
||||
// Attempt to populate versions with "auto featured" versions
|
||||
if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) {
|
||||
@@ -155,7 +155,7 @@ pub async fn version_list(
|
||||
}
|
||||
}
|
||||
|
||||
response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||
response.sort();
|
||||
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
||||
|
||||
let response = filter_authorized_versions(response, &user_option, &pool).await?;
|
||||
@@ -306,6 +306,7 @@ pub struct EditVersion {
|
||||
pub downloads: Option<u32>,
|
||||
pub status: Option<VersionStatus>,
|
||||
pub file_types: Option<Vec<EditVersionFileType>>,
|
||||
pub ordering: Option<Option<i32>>, //TODO: How do you actually pass this in json?
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -684,6 +685,20 @@ pub async fn version_edit(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ordering) = &new_version.ordering {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE versions
|
||||
SET ordering = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
ordering.to_owned() as Option<i32>,
|
||||
id as database::models::ids::VersionId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// delete any images no longer in the changelog
|
||||
let checkable_strings: Vec<&str> = vec![&new_version.changelog]
|
||||
.into_iter()
|
||||
|
||||
Reference in New Issue
Block a user