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:
+12
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,\n JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n 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,\n JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,\n JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', dependency_file_name)) filter (where d.dependency_type is not null) dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.date_published ASC;\n ",
|
"query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering,\n JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n 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,\n JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,\n JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', dependency_file_name)) filter (where d.dependency_type is not null) dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC;\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -65,26 +65,31 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
|
"name": "ordering",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
"name": "game_versions",
|
"name": "game_versions",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 13,
|
"ordinal": 14,
|
||||||
"name": "loaders",
|
"name": "loaders",
|
||||||
"type_info": "VarcharArray"
|
"type_info": "VarcharArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 14,
|
"ordinal": 15,
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 15,
|
"ordinal": 16,
|
||||||
"name": "hashes",
|
"name": "hashes",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 16,
|
"ordinal": 17,
|
||||||
"name": "dependencies",
|
"name": "dependencies",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
@@ -107,6 +112,7 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@@ -114,5 +120,5 @@
|
|||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a62767e812783e8836a11b22878a4248123f3fe212a876e192f549acd6edcb39"
|
"hash": "1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6"
|
||||||
}
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE versions\n SET ordering = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int4",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6"
|
||||||
|
}
|
||||||
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11\n )\n ",
|
"query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status, ordering\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11, $12\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -15,10 +15,11 @@
|
|||||||
"Int4",
|
"Int4",
|
||||||
"Varchar",
|
"Varchar",
|
||||||
"Bool",
|
"Bool",
|
||||||
"Varchar"
|
"Varchar",
|
||||||
|
"Int4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed"
|
"hash": "a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY date_published ASC\n ",
|
"query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY ordering ASC NULLS LAST, date_published ASC\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -31,5 +31,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a"
|
"hash": "defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE versions ADD COLUMN ordering int NULL;
|
||||||
@@ -28,6 +28,7 @@ pub struct VersionBuilder {
|
|||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
pub status: VersionStatus,
|
pub status: VersionStatus,
|
||||||
pub requested_status: Option<VersionStatus>,
|
pub requested_status: Option<VersionStatus>,
|
||||||
|
pub ordering: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -214,6 +215,7 @@ impl VersionBuilder {
|
|||||||
version_type: self.version_type,
|
version_type: self.version_type,
|
||||||
status: self.status,
|
status: self.status,
|
||||||
requested_status: self.requested_status,
|
requested_status: self.requested_status,
|
||||||
|
ordering: self.ordering,
|
||||||
};
|
};
|
||||||
|
|
||||||
version.insert(transaction).await?;
|
version.insert(transaction).await?;
|
||||||
@@ -317,7 +319,7 @@ impl VersionVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
pub id: VersionId,
|
pub id: VersionId,
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
@@ -332,6 +334,7 @@ pub struct Version {
|
|||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
pub status: VersionStatus,
|
pub status: VersionStatus,
|
||||||
pub requested_status: Option<VersionStatus>,
|
pub requested_status: Option<VersionStatus>,
|
||||||
|
pub ordering: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
@@ -344,12 +347,12 @@ impl Version {
|
|||||||
INSERT INTO versions (
|
INSERT INTO versions (
|
||||||
id, mod_id, author_id, name, version_number,
|
id, mod_id, author_id, name, version_number,
|
||||||
changelog, date_published, downloads,
|
changelog, date_published, downloads,
|
||||||
version_type, featured, status
|
version_type, featured, status, ordering
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7, $8,
|
$6, $7, $8,
|
||||||
$9, $10, $11
|
$9, $10, $11, $12
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as VersionId,
|
self.id as VersionId,
|
||||||
@@ -362,7 +365,8 @@ impl Version {
|
|||||||
self.downloads,
|
self.downloads,
|
||||||
&self.version_type,
|
&self.version_type,
|
||||||
self.featured,
|
self.featured,
|
||||||
self.status.as_str()
|
self.status.as_str(),
|
||||||
|
self.ordering
|
||||||
)
|
)
|
||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.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,
|
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.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,
|
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,
|
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,
|
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
|
LEFT OUTER JOIN dependencies d on v.id = d.dependent_id
|
||||||
WHERE v.id = ANY($1)
|
WHERE v.id = ANY($1)
|
||||||
GROUP BY v.id
|
GROUP BY v.id
|
||||||
ORDER BY v.date_published ASC;
|
ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC;
|
||||||
",
|
",
|
||||||
&version_ids_parsed
|
&version_ids_parsed
|
||||||
)
|
)
|
||||||
@@ -593,6 +597,7 @@ impl Version {
|
|||||||
status: VersionStatus::from_string(&v.status),
|
status: VersionStatus::from_string(&v.status),
|
||||||
requested_status: v.requested_status
|
requested_status: v.requested_status
|
||||||
.map(|x| VersionStatus::from_string(&x)),
|
.map(|x| VersionStatus::from_string(&x)),
|
||||||
|
ordering: v.ordering,
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -851,7 +856,7 @@ impl Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct QueryVersion {
|
pub struct QueryVersion {
|
||||||
pub inner: Version,
|
pub inner: Version,
|
||||||
|
|
||||||
@@ -861,7 +866,7 @@ pub struct QueryVersion {
|
|||||||
pub dependencies: Vec<QueryDependency>,
|
pub dependencies: Vec<QueryDependency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct QueryDependency {
|
pub struct QueryDependency {
|
||||||
pub project_id: Option<ProjectId>,
|
pub project_id: Option<ProjectId>,
|
||||||
pub version_id: Option<VersionId>,
|
pub version_id: Option<VersionId>,
|
||||||
@@ -869,7 +874,7 @@ pub struct QueryDependency {
|
|||||||
pub dependency_type: String,
|
pub dependency_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct QueryFile {
|
pub struct QueryFile {
|
||||||
pub id: FileId,
|
pub id: FileId,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
@@ -892,3 +897,84 @@ pub struct SingleFile {
|
|||||||
pub size: u32,
|
pub size: u32,
|
||||||
pub file_type: Option<FileType>,
|
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>,
|
pub game_versions: Vec<GameVersion>,
|
||||||
/// The loaders that this version works on
|
/// The loaders that this version works on
|
||||||
pub loaders: Vec<Loader>,
|
pub loaders: Vec<Loader>,
|
||||||
|
/// Ordering override, lower is returned first
|
||||||
|
pub ordering: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<QueryVersion> for Version {
|
impl From<QueryVersion> for Version {
|
||||||
@@ -515,6 +517,7 @@ impl From<QueryVersion> for Version {
|
|||||||
"alpha" => VersionType::Alpha,
|
"alpha" => VersionType::Alpha,
|
||||||
_ => VersionType::Release,
|
_ => VersionType::Release,
|
||||||
},
|
},
|
||||||
|
ordering: v.ordering,
|
||||||
|
|
||||||
status: v.status,
|
status: v.status,
|
||||||
requested_status: v.requested_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")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum FileType {
|
pub enum FileType {
|
||||||
RequiredResourcePack,
|
RequiredResourcePack,
|
||||||
|
|||||||
+1
-1
@@ -100,7 +100,7 @@ pub async fn maven_metadata(
|
|||||||
SELECT id, version_number, version_type
|
SELECT id, version_number, version_type
|
||||||
FROM versions
|
FROM versions
|
||||||
WHERE mod_id = $1 AND status = ANY($2)
|
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,
|
project.inner.id as database::models::ids::ProjectId,
|
||||||
&*crate::models::projects::VersionStatus::iterator()
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
|||||||
@@ -964,6 +964,7 @@ async fn create_initial_version(
|
|||||||
status: VersionStatus::Listed,
|
status: VersionStatus::Listed,
|
||||||
version_type: version_data.release_channel.to_string(),
|
version_type: version_data.release_channel.to_string(),
|
||||||
requested_status: None,
|
requested_status: None,
|
||||||
|
ordering: version_data.ordering,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(version)
|
Ok(version)
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ pub struct InitialVersionData {
|
|||||||
#[validate(length(max = 10))]
|
#[validate(length(max = 10))]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub uploaded_images: Vec<ImageId>,
|
pub uploaded_images: Vec<ImageId>,
|
||||||
|
|
||||||
|
// The ordering relative to other versions
|
||||||
|
pub ordering: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@@ -316,6 +319,7 @@ async fn version_create_inner(
|
|||||||
featured: version_create_data.featured,
|
featured: version_create_data.featured,
|
||||||
status: version_create_data.status,
|
status: version_create_data.status,
|
||||||
requested_status: None,
|
requested_status: None,
|
||||||
|
ordering: version_create_data.ordering,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -427,6 +431,7 @@ async fn version_create_inner(
|
|||||||
version_type: version_data.release_channel,
|
version_type: version_data.release_channel,
|
||||||
status: builder.status,
|
status: builder.status,
|
||||||
requested_status: builder.requested_status,
|
requested_status: builder.requested_status,
|
||||||
|
ordering: builder.ordering,
|
||||||
files: builder
|
files: builder
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ pub async fn get_update_from_hash(
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
})
|
})
|
||||||
.sorted_by(|a, b| a.inner.date_published.cmp(&b.inner.date_published))
|
.sorted()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Some(first) = versions.pop() {
|
if let Some(first) = versions.pop() {
|
||||||
@@ -522,7 +522,7 @@ pub async fn update_files(
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
})
|
})
|
||||||
.sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published))
|
.sorted()
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
if let Some(version) = version {
|
if let Some(version) = version {
|
||||||
@@ -629,7 +629,7 @@ pub async fn update_individual_files(
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
})
|
})
|
||||||
.sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published))
|
.sorted()
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
if let Some(version) = version {
|
if let Some(version) = version {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ pub async fn version_list(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.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
|
// Attempt to populate versions with "auto featured" versions
|
||||||
if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) {
|
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);
|
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
||||||
|
|
||||||
let response = filter_authorized_versions(response, &user_option, &pool).await?;
|
let response = filter_authorized_versions(response, &user_option, &pool).await?;
|
||||||
@@ -306,6 +306,7 @@ pub struct EditVersion {
|
|||||||
pub downloads: Option<u32>,
|
pub downloads: Option<u32>,
|
||||||
pub status: Option<VersionStatus>,
|
pub status: Option<VersionStatus>,
|
||||||
pub file_types: Option<Vec<EditVersionFileType>>,
|
pub file_types: Option<Vec<EditVersionFileType>>,
|
||||||
|
pub ordering: Option<Option<i32>>, //TODO: How do you actually pass this in json?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[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
|
// delete any images no longer in the changelog
|
||||||
let checkable_strings: Vec<&str> = vec![&new_version.changelog]
|
let checkable_strings: Vec<&str> = vec![&new_version.changelog]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::rc::Rc;
|
|||||||
pub mod organization;
|
pub mod organization;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod team;
|
pub mod team;
|
||||||
|
pub mod version;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApiV2 {
|
pub struct ApiV2 {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use actix_http::{header::AUTHORIZATION, StatusCode};
|
||||||
|
use actix_web::{dev::ServiceResponse, test};
|
||||||
|
use labrinth::models::projects::Version;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::common::{self, actix::AppendsMultipart, asserts::assert_status};
|
||||||
|
|
||||||
|
use super::ApiV2;
|
||||||
|
|
||||||
|
pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {
|
||||||
|
let serialized = serde_json::to_string(&elements).unwrap();
|
||||||
|
urlencoding::encode(&serialized).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiV2 {
|
||||||
|
pub async fn create_default_version(
|
||||||
|
&self,
|
||||||
|
project_id: &str,
|
||||||
|
ordering: Option<i32>,
|
||||||
|
pat: &str,
|
||||||
|
) -> Version {
|
||||||
|
let json_data = json!(
|
||||||
|
{
|
||||||
|
"project_id": project_id,
|
||||||
|
"file_parts": ["basic-mod-different.jar"],
|
||||||
|
"version_number": "1.2.3.4",
|
||||||
|
"version_title": "start",
|
||||||
|
"dependencies": [],
|
||||||
|
"game_versions": ["1.20.1"] ,
|
||||||
|
"release_channel": "release",
|
||||||
|
"loaders": ["fabric"],
|
||||||
|
"featured": true,
|
||||||
|
"ordering": ordering,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let json_segment = common::actix::MultipartSegment {
|
||||||
|
name: "data".to_string(),
|
||||||
|
filename: None,
|
||||||
|
content_type: Some("application/json".to_string()),
|
||||||
|
data: common::actix::MultipartSegmentData::Text(
|
||||||
|
serde_json::to_string(&json_data).unwrap(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let file_segment = common::actix::MultipartSegment {
|
||||||
|
name: "basic-mod-different.jar".to_string(),
|
||||||
|
filename: Some("basic-mod.jar".to_string()),
|
||||||
|
content_type: Some("application/java-archive".to_string()),
|
||||||
|
data: common::actix::MultipartSegmentData::Binary(
|
||||||
|
include_bytes!("../../../tests/files/basic-mod-different.jar").to_vec(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = test::TestRequest::post()
|
||||||
|
.uri("/v2/version")
|
||||||
|
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
|
||||||
|
.append_header((AUTHORIZATION, pat))
|
||||||
|
.to_request();
|
||||||
|
let resp = self.call(request).await;
|
||||||
|
assert_status(&resp, StatusCode::OK);
|
||||||
|
test::read_body_json(resp).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_versions(&self, version_ids: Vec<String>, pat: &str) -> Vec<Version> {
|
||||||
|
let ids = url_encode_json_serialized_vec(&version_ids);
|
||||||
|
let request = test::TestRequest::get()
|
||||||
|
.uri(&format!("/v2/versions?ids={}", ids))
|
||||||
|
.append_header((AUTHORIZATION, pat))
|
||||||
|
.to_request();
|
||||||
|
let resp = self.call(request).await;
|
||||||
|
assert_status(&resp, StatusCode::OK);
|
||||||
|
test::read_body_json(resp).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn edit_version_ordering(
|
||||||
|
&self,
|
||||||
|
version_id: &str,
|
||||||
|
ordering: Option<i32>,
|
||||||
|
pat: &str,
|
||||||
|
) -> ServiceResponse {
|
||||||
|
let request = test::TestRequest::patch()
|
||||||
|
.uri(&format!("/v2/version/{version_id}"))
|
||||||
|
.set_json(json!(
|
||||||
|
{
|
||||||
|
"ordering": ordering
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.append_header((AUTHORIZATION, pat))
|
||||||
|
.to_request();
|
||||||
|
self.call(request).await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,23 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use crate::common::get_json_val_str;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
pub fn assert_status(response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode) {
|
pub fn assert_status(response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode) {
|
||||||
assert_eq!(response.status(), status, "{:#?}", response.response());
|
assert_eq!(response.status(), status, "{:#?}", response.response());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_version_ids(
|
||||||
|
versions: &[labrinth::models::projects::Version],
|
||||||
|
expected_ids: Vec<String>,
|
||||||
|
) {
|
||||||
|
let version_ids = versions
|
||||||
|
.iter()
|
||||||
|
.map(|v| get_json_val_str(v.id))
|
||||||
|
.collect_vec();
|
||||||
|
assert_eq!(version_ids, expected_ids);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn assert_any_status_except(
|
pub fn assert_any_status_except(
|
||||||
response: &actix_web::dev::ServiceResponse,
|
response: &actix_web::dev::ServiceResponse,
|
||||||
status: actix_http::StatusCode,
|
status: actix_http::StatusCode,
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
use crate::common::{asserts::assert_status, get_json_val_str};
|
||||||
|
use actix_http::StatusCode;
|
||||||
|
use common::{
|
||||||
|
asserts::assert_version_ids, database::USER_USER_PAT, environment::with_test_environment,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn can_create_version_with_ordering() {
|
||||||
|
with_test_environment(|env| async move {
|
||||||
|
let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone();
|
||||||
|
|
||||||
|
let new_version_id = get_json_val_str(
|
||||||
|
env.v2
|
||||||
|
.create_default_version(&alpha_project_id, Some(1), USER_USER_PAT)
|
||||||
|
.await
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
let versions = env
|
||||||
|
.v2
|
||||||
|
.get_versions(vec![new_version_id.clone()], USER_USER_PAT)
|
||||||
|
.await;
|
||||||
|
assert_eq!(versions[0].ordering, Some(1));
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn edit_version_ordering_works() {
|
||||||
|
with_test_environment(|env| async move {
|
||||||
|
let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone();
|
||||||
|
|
||||||
|
let resp = env
|
||||||
|
.v2
|
||||||
|
.edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT)
|
||||||
|
.await;
|
||||||
|
assert_status(&resp, StatusCode::NO_CONTENT);
|
||||||
|
|
||||||
|
let versions = env
|
||||||
|
.v2
|
||||||
|
.get_versions(vec![alpha_version_id.clone()], USER_USER_PAT)
|
||||||
|
.await;
|
||||||
|
assert_eq!(versions[0].ordering, Some(10));
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn version_ordering_for_specified_orderings_orders_lower_order_first() {
|
||||||
|
with_test_environment(|env| async move {
|
||||||
|
let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone();
|
||||||
|
let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone();
|
||||||
|
let new_version_id = get_json_val_str(
|
||||||
|
env.v2
|
||||||
|
.create_default_version(&alpha_project_id, Some(1), USER_USER_PAT)
|
||||||
|
.await
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
env.v2
|
||||||
|
.edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let versions = env
|
||||||
|
.v2
|
||||||
|
.get_versions(
|
||||||
|
vec![alpha_version_id.clone(), new_version_id.clone()],
|
||||||
|
USER_USER_PAT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_version_ids(&versions, vec![new_version_id, alpha_version_id]);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn version_ordering_when_unspecified_orders_oldest_first() {
|
||||||
|
with_test_environment(|env| async move {
|
||||||
|
let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone();
|
||||||
|
let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone();
|
||||||
|
let new_version_id = get_json_val_str(
|
||||||
|
env.v2
|
||||||
|
.create_default_version(&alpha_project_id, None, USER_USER_PAT)
|
||||||
|
.await
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
let versions = env
|
||||||
|
.v2
|
||||||
|
.get_versions(
|
||||||
|
vec![alpha_version_id.clone(), new_version_id.clone()],
|
||||||
|
USER_USER_PAT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_version_ids(&versions, vec![alpha_version_id, new_version_id]);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn version_ordering_when_specified_orders_specified_before_unspecified() {
|
||||||
|
with_test_environment(|env| async move {
|
||||||
|
let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone();
|
||||||
|
let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone();
|
||||||
|
let new_version_id = get_json_val_str(
|
||||||
|
env.v2
|
||||||
|
.create_default_version(&alpha_project_id, Some(10000), USER_USER_PAT)
|
||||||
|
.await
|
||||||
|
.id,
|
||||||
|
);
|
||||||
|
env.v2
|
||||||
|
.edit_version_ordering(&alpha_version_id, None, USER_USER_PAT)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let versions = env
|
||||||
|
.v2
|
||||||
|
.get_versions(
|
||||||
|
vec![alpha_version_id.clone(), new_version_id.clone()],
|
||||||
|
USER_USER_PAT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_version_ids(&versions, vec![new_version_id, alpha_version_id]);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user