Skip to content

Commit d0d3927

Browse files
committed
feat(mods): rewrite mod updates
1 parent b5c6975 commit d0d3927

17 files changed

+1226
-642
lines changed
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use sqlx::PgConnection;
2+
3+
use crate::types::{
4+
api::ApiError,
5+
mod_json::ModJson,
6+
models::dependency::{DependencyImportance, FetchedDependency, ModVersionCompare},
7+
};
8+
9+
pub async fn create(
10+
mod_version_id: i32,
11+
json: &ModJson,
12+
conn: &mut PgConnection,
13+
) -> Result<Vec<FetchedDependency>, ApiError> {
14+
let dependencies = json.prepare_dependencies_for_create()?;
15+
if dependencies.is_empty() {
16+
return Ok(vec![]);
17+
}
18+
19+
let len = dependencies.len();
20+
let dependent_id = vec![mod_version_id; len];
21+
let mut dependency_id: Vec<String> = Vec::with_capacity(len);
22+
let mut version: Vec<String> = Vec::with_capacity(len);
23+
let mut compare: Vec<ModVersionCompare> = Vec::with_capacity(len);
24+
let mut importance: Vec<DependencyImportance> = Vec::with_capacity(len);
25+
26+
for i in dependencies {
27+
dependency_id.push(i.dependency_id);
28+
version.push(i.version);
29+
compare.push(i.compare);
30+
importance.push(i.importance);
31+
}
32+
33+
sqlx::query_as!(
34+
FetchedDependency,
35+
r#"INSERT INTO dependencies
36+
(dependent_id, dependency_id, version, compare, importance)
37+
SELECT * FROM UNNEST(
38+
$1::int4[],
39+
$2::text[],
40+
$3::text[],
41+
$4::version_compare[],
42+
$5::dependency_importance[]
43+
)
44+
RETURNING
45+
dependent_id as mod_version_id,
46+
dependency_id,
47+
version,
48+
compare as "compare: _",
49+
importance as "importance: _""#,
50+
&dependent_id,
51+
&dependency_id,
52+
&version,
53+
&compare as &[ModVersionCompare],
54+
&importance as &[DependencyImportance]
55+
)
56+
.fetch_all(conn)
57+
.await
58+
.inspect_err(|e| log::error!("Failed to insert dependencies: {}", e))
59+
.or(Err(ApiError::DbError))
60+
}
61+
62+
pub async fn clear(id: i32, conn: &mut PgConnection) -> Result<(), ApiError> {
63+
sqlx::query!(
64+
"DELETE FROM dependencies
65+
WHERE dependent_id = $1",
66+
id
67+
)
68+
.execute(conn)
69+
.await
70+
.inspect_err(|e| log::error!("Failed to clear deps: {}", e))
71+
.or(Err(ApiError::DbError))?;
72+
73+
Ok(())
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use sqlx::PgConnection;
2+
3+
use crate::types::{
4+
api::ApiError,
5+
mod_json::ModJson,
6+
models::{
7+
dependency::ModVersionCompare,
8+
incompatibility::{FetchedIncompatibility, IncompatibilityImportance},
9+
},
10+
};
11+
12+
pub async fn create(
13+
mod_version_id: i32,
14+
json: &ModJson,
15+
conn: &mut PgConnection,
16+
) -> Result<Vec<FetchedIncompatibility>, ApiError> {
17+
let incompats = json.prepare_incompatibilities_for_create()?;
18+
if incompats.is_empty() {
19+
return Ok(vec![]);
20+
}
21+
22+
let len = incompats.len();
23+
let mod_id = vec![mod_version_id; len];
24+
let mut incompatibility_id: Vec<String> = Vec::with_capacity(len);
25+
let mut version: Vec<String> = Vec::with_capacity(len);
26+
let mut compare: Vec<ModVersionCompare> = Vec::with_capacity(len);
27+
let mut importance: Vec<IncompatibilityImportance> = Vec::with_capacity(len);
28+
29+
for i in incompats {
30+
incompatibility_id.push(i.incompatibility_id);
31+
version.push(i.version);
32+
compare.push(i.compare);
33+
importance.push(i.importance);
34+
}
35+
36+
sqlx::query_as!(
37+
FetchedIncompatibility,
38+
r#"INSERT INTO incompatibilities
39+
(mod_id, incompatibility_id, version, compare, importance)
40+
SELECT * FROM UNNEST(
41+
$1::int4[],
42+
$2::text[],
43+
$3::text[],
44+
$4::version_compare[],
45+
$5::incompatibility_importance[]
46+
)
47+
RETURNING
48+
mod_id,
49+
incompatibility_id,
50+
version,
51+
compare as "compare: _",
52+
importance as "importance: _""#,
53+
&mod_id,
54+
&incompatibility_id,
55+
&version,
56+
&compare as &[ModVersionCompare],
57+
&importance as &[IncompatibilityImportance]
58+
)
59+
.fetch_all(conn)
60+
.await
61+
.inspect_err(|e| log::error!("Failed to insert dependencies: {}", e))
62+
.or(Err(ApiError::DbError))
63+
}
64+
65+
pub async fn clear(id: i32, conn: &mut PgConnection) -> Result<(), ApiError> {
66+
sqlx::query!(
67+
"DELETE FROM incompatibilities
68+
WHERE mod_id = $1",
69+
id
70+
)
71+
.execute(conn)
72+
.await
73+
.inspect_err(|e| log::error!("Failed to clear incompats: {}", e))
74+
.or(Err(ApiError::DbError))?;
75+
76+
Ok(())
77+
}

src/database/repository/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
pub mod auth_tokens;
2+
pub mod dependencies;
23
pub mod developers;
34
pub mod github_login_attempts;
45
pub mod github_web_logins;
6+
pub mod incompatibilities;
57
pub mod mod_downloads;
8+
pub mod mod_gd_versions;
69
pub mod mod_tags;
10+
pub mod mod_version_statuses;
711
pub mod mod_versions;
812
pub mod mods;
913
pub mod refresh_tokens;
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use sqlx::PgConnection;
2+
3+
use crate::types::{
4+
api::ApiError,
5+
mod_json::ModJson,
6+
models::mod_gd_version::{DetailedGDVersion, GDVersionEnum, VerPlatform},
7+
};
8+
9+
pub async fn create(
10+
mod_version_id: i32,
11+
json: &ModJson,
12+
conn: &mut PgConnection,
13+
) -> Result<DetailedGDVersion, ApiError> {
14+
let create = json.gd.to_create_payload(json);
15+
16+
let gd: Vec<GDVersionEnum> = create.iter().map(|x| x.gd).collect();
17+
let platform: Vec<VerPlatform> = create.iter().map(|x| x.platform).collect();
18+
let mod_id = vec![mod_version_id; create.len()];
19+
20+
sqlx::query!(
21+
"INSERT INTO mod_gd_versions
22+
(gd, platform, mod_id)
23+
SELECT * FROM UNNEST(
24+
$1::gd_version[],
25+
$2::gd_ver_platform[],
26+
$3::int4[]
27+
)",
28+
&gd as &[GDVersionEnum],
29+
&platform as &[VerPlatform],
30+
&mod_id
31+
)
32+
.execute(conn)
33+
.await
34+
.inspect_err(|e| log::error!("Failed to insert mod_gd_versions: {}", e))
35+
.or(Err(ApiError::DbError))?;
36+
37+
Ok(json.gd.clone())
38+
}
39+
40+
pub async fn clear(mod_version_id: i32, conn: &mut PgConnection) -> Result<(), ApiError> {
41+
sqlx::query!(
42+
"DELETE FROM mod_gd_versions mgv
43+
WHERE mgv.mod_id = $1",
44+
mod_version_id
45+
)
46+
.execute(&mut *conn)
47+
.await
48+
.inspect_err(|e| log::error!("Failed to remove GD versions: {}", e))
49+
.or(Err(ApiError::DbError))?;
50+
51+
Ok(())
52+
}

src/database/repository/mod_tags.rs

+118
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::types::api::ApiError;
2+
use crate::types::mod_json::ModJson;
23
use crate::types::models::tag::Tag;
34
use sqlx::PgConnection;
45

@@ -28,3 +29,120 @@ pub async fn get_all(conn: &mut PgConnection) -> Result<Vec<Tag>, ApiError> {
2829

2930
Ok(tags)
3031
}
32+
33+
pub async fn get_for_mod(id: &str, conn: &mut PgConnection) -> Result<Vec<Tag>, ApiError> {
34+
sqlx::query!(
35+
"SELECT
36+
id,
37+
name,
38+
display_name,
39+
is_readonly
40+
FROM mod_tags mt
41+
INNER JOIN mods_mod_tags mmt ON mmt.tag_id = mt.id
42+
WHERE mmt.mod_id = $1",
43+
id
44+
)
45+
.fetch_all(&mut *conn)
46+
.await
47+
.map_err(|e| {
48+
log::error!("mod_tags::get_tags failed: {}", e);
49+
ApiError::DbError
50+
})
51+
.map(|vec| {
52+
vec.into_iter()
53+
.map(|i| Tag {
54+
id: i.id,
55+
display_name: i.display_name.unwrap_or(i.name.clone()),
56+
name: i.name,
57+
is_readonly: i.is_readonly,
58+
})
59+
.collect()
60+
})
61+
}
62+
63+
pub async fn parse_tag_list(
64+
tags: &[String],
65+
conn: &mut PgConnection,
66+
) -> Result<Vec<Tag>, ApiError> {
67+
if tags.is_empty() {
68+
return Ok(vec![]);
69+
}
70+
71+
let db_tags = get_all(conn).await?;
72+
73+
let mut ret = Vec::new();
74+
for tag in tags {
75+
if let Some(t) = db_tags.iter().find(|t| t.name == *tag) {
76+
ret.push(t.clone());
77+
} else {
78+
return Err(ApiError::BadRequest(format!(
79+
"Tag '{}' isn't allowed. Only the following are allowed: '{}'",
80+
tag,
81+
db_tags
82+
.into_iter()
83+
.map(|t| t.name)
84+
.collect::<Vec<String>>()
85+
.join(", ")
86+
)));
87+
}
88+
}
89+
90+
Ok(ret)
91+
}
92+
93+
pub async fn update_for_mod(
94+
id: &str,
95+
tags: &[Tag],
96+
conn: &mut PgConnection,
97+
) -> Result<(), ApiError> {
98+
let existing = get_for_mod(id, &mut *conn).await?;
99+
100+
let insertable = tags
101+
.iter()
102+
.filter(|t| !existing.iter().any(|e| e.id == t.id))
103+
.map(|x| x.id)
104+
.collect::<Vec<_>>();
105+
106+
let deletable = existing
107+
.iter()
108+
.filter(|e| !tags.iter().any(|t| e.id == t.id))
109+
.map(|x| x.id)
110+
.collect::<Vec<_>>();
111+
112+
if !deletable.is_empty() {
113+
sqlx::query!(
114+
"DELETE FROM mods_mod_tags
115+
WHERE mod_id = $1
116+
AND tag_id = ANY($2)",
117+
id,
118+
&deletable
119+
)
120+
.execute(&mut *conn)
121+
.await
122+
.inspect_err(|e| log::error!("Failed to remove tags: {}", e))
123+
.or(Err(ApiError::DbError))?;
124+
}
125+
126+
if insertable.is_empty() {
127+
return Ok(());
128+
}
129+
130+
let mod_id = vec![id.into(); insertable.len()];
131+
132+
sqlx::query!(
133+
"INSERT INTO mods_mod_tags
134+
(mod_id, tag_id)
135+
SELECT * FROM UNNEST(
136+
$1::text[],
137+
$2::int4[]
138+
)",
139+
&mod_id,
140+
&insertable
141+
)
142+
.execute(&mut *conn)
143+
.await
144+
.inspect_err(|e| log::error!("Failed to insert tags: {}", e))
145+
.or(Err(ApiError::DbError))?;
146+
147+
Ok(())
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use sqlx::PgConnection;
2+
3+
use crate::types::{api::ApiError, models::mod_version_status::ModVersionStatusEnum};
4+
5+
pub async fn create(
6+
mod_version_id: i32,
7+
status: ModVersionStatusEnum,
8+
info: Option<String>,
9+
conn: &mut PgConnection,
10+
) -> Result<i32, ApiError> {
11+
sqlx::query!(
12+
"INSERT INTO mod_version_statuses
13+
(mod_version_id, status, info, admin_id)
14+
VALUES ($1, $2, $3, NULL)
15+
RETURNING id",
16+
mod_version_id,
17+
status as ModVersionStatusEnum,
18+
info
19+
)
20+
.fetch_one(conn)
21+
.await
22+
.inspect_err(|e| log::error!("Failed to create status: {}", e))
23+
.or(Err(ApiError::DbError))
24+
.map(|i| i.id)
25+
}

0 commit comments

Comments
 (0)