Skip to content

Commit

Permalink
Add support for the 'links' key in mod.json (#29)
Browse files Browse the repository at this point in the history
* feat(links): add saving of links

* feat(links): add links to mod payloads

* why not

* fix(links): handle some edge cases with updates
  • Loading branch information
Fleeym authored Aug 2, 2024
1 parent 8c64f69 commit 9ee2717
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 11 deletions.
3 changes: 3 additions & 0 deletions migrations/20240729184358_add_links_for_mods.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Add down migration script here

DROP TABLE mod_links;
10 changes: 10 additions & 0 deletions migrations/20240729184358_add_links_for_mods.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Add up migration script here

CREATE TABLE mod_links (
mod_id TEXT PRIMARY KEY NOT NULL,
community TEXT,
homepage TEXT,
source TEXT,

FOREIGN KEY (mod_id) REFERENCES mods(id)
);
36 changes: 36 additions & 0 deletions src/types/mod_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use image::{
DynamicImage, GenericImageView, ImageEncoder,
};
use regex::Regex;
use reqwest::Url;
use semver::Version;
use serde::Deserialize;
use std::io::BufReader;
Expand Down Expand Up @@ -59,6 +60,14 @@ pub struct ModJson {
pub changelog: Option<String>,
pub dependencies: Option<Vec<ModJsonDependency>>,
pub incompatibilities: Option<Vec<ModJsonIncompatibility>>,
pub links: Option<ModJsonLinks>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct ModJsonLinks {
pub community: Option<String>,
pub homepage: Option<String>,
pub source: Option<String>,
}

#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -366,6 +375,33 @@ impl ModJson {
"Mod id too long (max 64 characters)".to_string(),
));
}

if let Some(l) = &self.links {
if let Some(community) = &l.community {
if let Err(e) = Url::parse(community) {
return Err(ApiError::BadRequest(format!(
"Invalid community URL: {}. Reason: {}",
community, e
)));
}
}
if let Some(homepage) = &l.homepage {
if let Err(e) = Url::parse(homepage) {
return Err(ApiError::BadRequest(format!(
"Invalid homepage URL: {}. Reason: {}",
homepage, e
)));
}
}
if let Some(source) = &l.source {
if let Err(e) = Url::parse(source) {
return Err(ApiError::BadRequest(format!(
"Invalid source URL: {}. Reason: {}",
source, e
)));
}
}
}
Ok(())
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/types/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ pub mod github_login_attempt;
pub mod incompatibility;
pub mod mod_entity;
pub mod mod_gd_version;
pub mod mod_link;
pub mod mod_version;
pub mod mod_version_status;
pub mod tag;
pub mod stats;
pub mod tag;
58 changes: 48 additions & 10 deletions src/types/models/mod_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
},
types::{
api::{ApiError, PaginatedData},
mod_json::{self, ModJson},
mod_json::{self, ModJson, ModJsonLinks},
models::{
mod_version::ModVersion, mod_version_status::ModVersionStatusEnum,
},
Expand All @@ -23,11 +23,7 @@ use sqlx::{
use std::{collections::HashMap, io::{Cursor, Read}, str::FromStr};

use super::{
dependency::ResponseDependency,
developer::{Developer, FetchedDeveloper},
incompatibility::{Replacement, ResponseIncompatibility},
mod_gd_version::{DetailedGDVersion, GDVersionEnum, ModGDVersion, VerPlatform},
tag::Tag,
dependency::ResponseDependency, developer::{Developer, FetchedDeveloper}, incompatibility::{Replacement, ResponseIncompatibility}, mod_gd_version::{DetailedGDVersion, GDVersionEnum, ModGDVersion, VerPlatform}, mod_link::ModLinks, tag::Tag
};

#[derive(Serialize, Debug, sqlx::FromRow)]
Expand All @@ -43,6 +39,7 @@ pub struct Mod {
pub changelog: Option<String>,
pub created_at: String,
pub updated_at: String,
pub links: Option<ModLinks>
}

#[derive(Serialize, Debug)]
Expand Down Expand Up @@ -397,6 +394,7 @@ impl Mod {
let ids: Vec<_> = records.iter().map(|x| x.id.clone()).collect();
let versions = ModVersion::get_latest_for_mods(pool, ids.clone(), query.gd, platforms, query.geode.as_ref()).await?;
let developers = Developer::fetch_for_mods(&ids, pool).await?;
let links = ModLinks::fetch_for_mods(&ids, pool).await?;
let mut mod_version_ids: Vec<i32> = vec![];
for (_, mod_version) in versions.iter() {
mod_version_ids.push(mod_version.id);
Expand All @@ -414,6 +412,8 @@ impl Mod {

let devs = developers.get(&x.id).cloned().unwrap_or_default();
let tags = tags.get(&x.id).cloned().unwrap_or_default();
let links = links.iter().find(|link| link.mod_id == x.id).cloned();

Mod {
id: x.id.clone(),
repository: x.repository.clone(),
Expand All @@ -426,6 +426,7 @@ impl Mod {
updated_at: x.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true),
about: None,
changelog: None,
links
}
})
.collect();
Expand All @@ -440,6 +441,7 @@ impl Mod {
let ids: Vec<_> = records.iter().map(|x| x.id.clone()).collect();
let versions = ModVersion::get_pending_for_mods(&ids, pool).await?;
let developers = Developer::fetch_for_mods(&ids, pool).await?;
let links = ModLinks::fetch_for_mods(&ids, pool).await?;
let mut mod_version_ids: Vec<i32> = vec![];
for (_, mod_version) in versions.iter() {
mod_version_ids.append(&mut mod_version.iter().map(|x| x.id).collect());
Expand All @@ -457,6 +459,8 @@ impl Mod {

let devs = developers.get(&x.id).cloned().unwrap_or_default();
let tags = tags.get(&x.id).cloned().unwrap_or_default();
let links = links.iter().find(|link| link.mod_id == x.id).cloned();

Mod {
id: x.id.clone(),
repository: x.repository.clone(),
Expand All @@ -469,6 +473,7 @@ impl Mod {
updated_at: x.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true),
about: x.about,
changelog: x.changelog,
links
}
})
.collect::<Vec<Mod>>();
Expand Down Expand Up @@ -622,10 +627,11 @@ impl Mod {
info: Some(x.info.clone()),
})
.collect();
let ids = versions.iter().map(|x| x.id).collect();
let gd = ModGDVersion::get_for_mod_versions(&ids, pool).await?;
let tags = Tag::get_tags_for_mod(id, pool).await?;
let devs = Developer::fetch_for_mod(id, pool).await?;
let ids: Vec<i32> = versions.iter().map(|x| x.id).collect();
let gd: HashMap<i32, DetailedGDVersion> = ModGDVersion::get_for_mod_versions(&ids, pool).await?;
let tags: Vec<String> = Tag::get_tags_for_mod(id, pool).await?;
let devs: Vec<Developer> = Developer::fetch_for_mod(id, pool).await?;
let links: Option<ModLinks> = ModLinks::fetch(id, pool).await?;

for i in &mut versions {
let gd_versions = gd.get(&i.id).cloned().unwrap_or_default();
Expand All @@ -648,6 +654,7 @@ impl Mod {
.to_rfc3339_opts(SecondsFormat::Secs, true),
about: records[0].about.clone(),
changelog: records[0].changelog.clone(),
links
};
Ok(Some(mod_entity))
}
Expand All @@ -673,6 +680,19 @@ impl Mod {
let dev_verified = developer.verified;

Mod::create(json, developer, pool).await?;
if let Some(l) = &json.links {
if l.community.is_some()
|| l.homepage.is_some()
|| l.source.is_some() {
ModLinks::upsert_for_mod(
&json.id,
l.community.clone(),
l.homepage.clone(),
l.source.clone(),
pool
).await?;
}
}
ModVersion::create_from_json(json, dev_verified, pool).await?;
Ok(())
}
Expand Down Expand Up @@ -1000,6 +1020,24 @@ impl Mod {
}
}

let links = ModLinks::fetch(&json.id, pool).await?;

if links.is_some() || json.links.is_some() {
let links = json.links.clone().unwrap_or(ModJsonLinks {
community: None,
source: None,
homepage: None
});
ModLinks::upsert_for_mod(
&json.id,
links.community,
links.homepage,
links.source,
pool
).await?;
}


Ok(())
}

Expand Down
Loading

0 comments on commit 9ee2717

Please sign in to comment.