Skip to content

Commit 7eee019

Browse files
authored
Merge pull request #21 from geode-sdk/superseded
Add support for replacing mods through incompatibilities
2 parents 2ad4d2a + b5717fb commit 7eee019

File tree

6 files changed

+277
-17
lines changed

6 files changed

+277
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Add up migration script here
2+
3+
CREATE COLLATION IF NOT EXISTS en_natural (
4+
LOCALE = 'en-US-u-kn-true',
5+
PROVIDER = 'icu'
6+
);
7+
8+
ALTER TABLE incompatibilities
9+
ALTER COLUMN version
10+
SET DATA TYPE TEXT
11+
COLLATE "en_natural";
12+
13+
ALTER TABLE dependencies
14+
ALTER COLUMN version
15+
SET DATA TYPE TEXT
16+
COLLATE "en_natural";
17+
18+
ALTER TABLE mod_versions
19+
ALTER COLUMN version
20+
SET DATA TYPE TEXT
21+
COLLATE "en_natural";

openapi.yml

+103-2
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,13 @@ paths:
452452
content:
453453
application/json:
454454
schema:
455-
type: string
455+
type: object
456+
properties:
457+
error:
458+
type: "null"
459+
payload:
460+
type: array
461+
items: "#/components/schemas/ModUpdate"
456462

457463
/v1/mods/{id}/developers:
458464
post:
@@ -677,6 +683,80 @@ components:
677683
- type: "null"
678684
- $ref: "#/components/schemas/GDVersionString"
679685

686+
ModUpdate:
687+
type: object
688+
properties:
689+
id:
690+
$ref: "#/components/schemas/ModID"
691+
version:
692+
$ref: "#/components/schemas/ModVersionString"
693+
download_link:
694+
type: string
695+
examples:
696+
- "https://api.geode-sdk.com/v1/mods/geode.nodeids/versions/1.0.0/download"
697+
- "https://api.geode-sdk.com/v1/mods/geode.devtools/versions/1.0.0/download"
698+
replacement:
699+
oneOf:
700+
- "null"
701+
- $ref: "#/components/schemas/ModReplacement"
702+
dependencies:
703+
type: array
704+
items:
705+
$ref: "#/components/schemas/ModDependency"
706+
incompatibilities:
707+
type: array
708+
items:
709+
$ref: "#/components/schemas/ModIncompatibility"
710+
711+
ModReplacement:
712+
type: object
713+
properties:
714+
id:
715+
$ref: "#/components/schemas/ModID"
716+
version:
717+
$ref: "#/components/schemas/ModVersionString"
718+
download_link:
719+
type: string
720+
examples:
721+
- "https://api.geode-sdk.com/v1/mods/geode.nodeids/versions/1.0.0/download"
722+
- "https://api.geode-sdk.com/v1/mods/geode.devtools/versions/1.0.0/download"
723+
dependencies:
724+
type: array
725+
items:
726+
$ref: "#/components/schemas/ModDependency"
727+
incompatibilities:
728+
type: array
729+
items:
730+
$ref: "#/components/schemas/ModIncompatibility"
731+
732+
ModDependency:
733+
type: object
734+
properties:
735+
mod_id:
736+
$ref: "#/components/schemas/ModID"
737+
version:
738+
type: string
739+
examples:
740+
- ">1.0.0"
741+
- "*"
742+
- "<=2.0.0"
743+
importance:
744+
$ref: "#/components/schemas/DependencyImportance"
745+
746+
ModIncompatibility:
747+
type: object
748+
properties:
749+
mod_id:
750+
$ref: "#/components/schemas/ModID"
751+
version:
752+
type: string
753+
examples:
754+
- ">1.0.0"
755+
- "*"
756+
- "<=2.0.0"
757+
importance:
758+
$ref: "#/components/schemas/IncompatibilityImportance"
759+
680760
Mod:
681761
type: object
682762
properties:
@@ -756,9 +836,16 @@ components:
756836
type: array
757837
items:
758838
type: string
759-
760839
mod_id:
761840
$ref: "#/components/schemas/ModID"
841+
dependencies:
842+
type: array
843+
items:
844+
$ref: "#/components/schemas/ModDependency"
845+
incompatibilities:
846+
type: array
847+
items:
848+
$ref: "#/components/schemas/ModIncompatibility"
762849

763850
ModVersionStatus:
764851
type: string
@@ -768,6 +855,20 @@ components:
768855
- pending
769856
- unlisted
770857

858+
DependencyImportance:
859+
type: string
860+
enum:
861+
- suggested
862+
- recommended
863+
- required
864+
865+
IncompatibilityImportance:
866+
type: string
867+
enum:
868+
- breaking
869+
- conflicting
870+
- superseded
871+
771872
ModDeveloper:
772873
type: object
773874
properties:

src/endpoints/mods.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use sqlx::Acquire;
55
use crate::extractors::auth::Auth;
66
use crate::types::api::{create_download_link, ApiError, ApiResponse};
77
use crate::types::mod_json::ModJson;
8+
use crate::types::models::incompatibility::Incompatibility;
89
use crate::types::models::mod_entity::{download_geode_file, Mod, ModUpdate};
910
use crate::types::models::mod_gd_version::{GDVersionEnum, VerPlatform};
1011
use crate::types::models::mod_version_status::ModVersionStatusEnum;
@@ -139,8 +140,15 @@ pub async fn get_mod_updates(
139140

140141
let platforms: Vec<VerPlatform> = vec![];
141142

142-
let mut result: Vec<ModUpdate> = Mod::get_updates(ids, platforms, &mut pool).await?;
143+
let mut result: Vec<ModUpdate> = Mod::get_updates(&ids, platforms, &mut pool).await?;
144+
let replacements = Incompatibility::get_supersedes_for(&ids, &mut pool).await?;
145+
143146
for i in &mut result {
147+
if let Some(replacement) = replacements.get(&i.id) {
148+
let mut clone = replacement.clone();
149+
clone.download_link = create_download_link(&data.app_url, &clone.id, &clone.version);
150+
i.replacement = Some(clone);
151+
}
144152
i.download_link = create_download_link(&data.app_url, &i.id, &i.version);
145153
}
146154

src/types/models/incompatibility.rs

+74-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use crate::types::models::dependency::ModVersionCompare;
55
use serde::{Deserialize, Serialize};
66
use sqlx::{PgConnection, Postgres, QueryBuilder};
77

8+
use super::dependency::ResponseDependency;
9+
810
#[derive(sqlx::FromRow, Clone, Debug)]
911
pub struct FetchedIncompatibility {
1012
pub mod_id: i32,
@@ -29,7 +31,18 @@ pub struct Incompatibility {
2931
pub importance: IncompatibilityImportance,
3032
}
3133

32-
#[derive(sqlx::Type, Debug, Serialize, Clone, Copy, Deserialize)]
34+
#[derive(Debug, Serialize, Clone)]
35+
pub struct Replacement {
36+
pub id: String,
37+
pub version: String,
38+
#[serde(skip_serializing)]
39+
pub replacement_id: i32,
40+
pub download_link: String,
41+
pub dependencies: Vec<ResponseDependency>,
42+
pub incompatibilities: Vec<ResponseIncompatibility>,
43+
}
44+
45+
#[derive(sqlx::Type, Debug, Serialize, Clone, Copy, Deserialize, PartialEq)]
3346
#[sqlx(type_name = "incompatibility_importance", rename_all = "lowercase")]
3447
#[serde(rename_all = "lowercase")]
3548
pub enum IncompatibilityImportance {
@@ -128,7 +141,7 @@ impl Incompatibility {
128141
icp.incompatibility_id, icp.mod_id, icp.version FROM incompatibilities icp
129142
INNER JOIN mod_versions mv ON mv.id = icp.mod_id
130143
WHERE mv.id = ANY($1)"#,
131-
&ids
144+
&ids,
132145
)
133146
.fetch_all(&mut *pool)
134147
.await
@@ -147,4 +160,63 @@ impl Incompatibility {
147160

148161
Ok(ret)
149162
}
163+
164+
pub async fn get_supersedes_for(
165+
ids: &Vec<String>,
166+
pool: &mut PgConnection,
167+
) -> Result<HashMap<String, Replacement>, ApiError> {
168+
let mut ret: HashMap<String, Replacement> = HashMap::new();
169+
let r = match sqlx::query!(
170+
r#"
171+
SELECT
172+
q.replaced,
173+
q.replacement,
174+
q.replacement_version,
175+
q.replacement_id
176+
FROM (
177+
SELECT
178+
replaced.incompatibility_id AS replaced,
179+
replacement.mod_id AS replacement,
180+
replacement.version AS replacement_version,
181+
replacement.id AS replacement_id,
182+
ROW_NUMBER() OVER(
183+
partition by replacement.mod_id
184+
order by replacement.version desc
185+
) rn
186+
FROM incompatibilities replaced
187+
INNER JOIN mod_versions replacement ON replacement.id = replaced.mod_id
188+
INNER JOIN mod_version_statuses replacement_status
189+
ON replacement.status_id = replacement_status.id
190+
WHERE replaced.importance = 'superseded'
191+
AND replacement_status.status = 'accepted'
192+
AND replaced.incompatibility_id = ANY($1)
193+
ORDER BY replacement.id DESC, replacement.version DESC
194+
) q
195+
WHERE q.rn = 1
196+
"#,
197+
ids
198+
)
199+
.fetch_all(&mut *pool)
200+
.await
201+
{
202+
Err(e) => {
203+
log::error!("Failed to fetch supersedes. ERR: {}", e);
204+
return Err(ApiError::DbError);
205+
}
206+
Ok(r) => r,
207+
};
208+
209+
for i in r.iter() {
210+
ret.entry(i.replaced.clone()).or_insert(Replacement {
211+
id: i.replacement.clone(),
212+
version: i.replacement_version.clone(),
213+
replacement_id: i.replacement_id,
214+
// Should be completed later
215+
download_link: "".to_string(),
216+
dependencies: vec![],
217+
incompatibilities: vec![],
218+
});
219+
}
220+
Ok(ret)
221+
}
150222
}

src/types/models/mod_entity.rs

+25-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
mod_json::ModJson,
99
models::{
1010
dependency::{Dependency, FetchedDependency},
11-
incompatibility::{FetchedIncompatibility, Incompatibility},
11+
incompatibility::{FetchedIncompatibility, Incompatibility, IncompatibilityImportance},
1212
mod_version::ModVersion, mod_version_status::ModVersionStatusEnum,
1313
},
1414
},
@@ -26,7 +26,7 @@ use std::{collections::HashMap, io::Cursor, str::FromStr};
2626
use super::{
2727
dependency::ResponseDependency,
2828
developer::{Developer, FetchedDeveloper},
29-
incompatibility::ResponseIncompatibility,
29+
incompatibility::{Replacement, ResponseIncompatibility},
3030
mod_gd_version::{DetailedGDVersion, GDVersionEnum, ModGDVersion, VerPlatform},
3131
tag::Tag,
3232
};
@@ -50,7 +50,10 @@ pub struct Mod {
5050
pub struct ModUpdate {
5151
pub id: String,
5252
pub version: String,
53+
#[serde(skip_serializing)]
54+
pub mod_version_id: i32,
5355
pub download_link: String,
56+
pub replacement: Option<Replacement>,
5457
pub dependencies: Vec<ResponseDependency>,
5558
pub incompatibilities: Vec<ResponseIncompatibility>,
5659
}
@@ -1092,18 +1095,27 @@ impl Mod {
10921095
}
10931096

10941097
pub async fn get_updates(
1095-
ids: Vec<String>,
1098+
ids: &Vec<String>,
10961099
platforms: Vec<VerPlatform>,
10971100
pool: &mut PgConnection,
10981101
) -> Result<Vec<ModUpdate>, ApiError> {
10991102
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
1100-
"SELECT q.id, q.version, q.mod_version_id FROM (SELECT m.id, mv.version, mv.id as mod_version_id,
1101-
row_number() over (partition by m.id order by mv.id desc) rn FROM mods m
1102-
INNER JOIN mod_versions mv ON mv.mod_id = m.id
1103-
INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id
1104-
WHERE mvs.status = 'accepted' AND m.id = ANY(",
1103+
r#"SELECT
1104+
q.id,
1105+
q.inner_version as version,
1106+
q.mod_version_id
1107+
FROM (
1108+
SELECT m.id,
1109+
mv.id as mod_version_id,
1110+
mv.version as inner_version,
1111+
row_number() over (partition by m.id order by mv.version desc) rn
1112+
FROM mods m
1113+
INNER JOIN mod_versions mv ON mv.mod_id = m.id
1114+
INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id
1115+
WHERE mvs.status = 'accepted'
1116+
AND m.id = ANY("#,
11051117
);
1106-
query_builder.push_bind(&ids);
1118+
query_builder.push_bind(ids);
11071119
query_builder.push(") ");
11081120

11091121
if !platforms.is_empty() {
@@ -1150,8 +1162,9 @@ impl Mod {
11501162

11511163
for r in result {
11521164
let update = ModUpdate {
1153-
id: r.id,
1165+
id: r.id.clone(),
11541166
version: r.version,
1167+
mod_version_id: r.mod_version_id,
11551168
download_link: "".to_string(),
11561169
dependencies: deps
11571170
.get(&r.mod_version_id)
@@ -1167,6 +1180,7 @@ impl Mod {
11671180
.iter()
11681181
.map(|x| x.to_response())
11691182
.collect(),
1183+
replacement: None
11701184
};
11711185
ret.push(update);
11721186
}
@@ -1213,4 +1227,4 @@ async fn get_download_size(url: &str) -> Result<u64, ApiError> {
12131227
"Couldn't extract download size from URL".to_string(),
12141228
)),
12151229
}
1216-
}
1230+
}

0 commit comments

Comments
 (0)