forked from didirus/AstralRinth
* Improve curseforge and local indexing This should make curseforge indexing more efficient, and reuses some of the normal local indexing for the queued indexing of recently created mods. * Unify impls for single and multiple routes for mods and versions This uses the same backend for the single and multiple query routes so that they no longer return inconsistent information. * Cache valid curseforge mod ids to reduce request load This caches the ids of minecraft mods and reuses them on indexing to reduce the amount of unused addons that are returned.
356 lines
9.7 KiB
Rust
356 lines
9.7 KiB
Rust
use super::ids::*;
|
|
|
|
pub struct ModBuilder {
|
|
pub mod_id: ModId,
|
|
pub team_id: TeamId,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub body_url: String,
|
|
pub icon_url: Option<String>,
|
|
pub issues_url: Option<String>,
|
|
pub source_url: Option<String>,
|
|
pub wiki_url: Option<String>,
|
|
pub categories: Vec<CategoryId>,
|
|
pub initial_versions: Vec<super::version_item::VersionBuilder>,
|
|
pub status: StatusId,
|
|
}
|
|
|
|
impl ModBuilder {
|
|
pub async fn insert(
|
|
self,
|
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
) -> Result<ModId, super::DatabaseError> {
|
|
let mod_struct = Mod {
|
|
id: self.mod_id,
|
|
team_id: self.team_id,
|
|
title: self.title,
|
|
description: self.description,
|
|
body_url: self.body_url,
|
|
published: chrono::Utc::now(),
|
|
updated: chrono::Utc::now(),
|
|
status: self.status,
|
|
downloads: 0,
|
|
icon_url: self.icon_url,
|
|
issues_url: self.issues_url,
|
|
source_url: self.source_url,
|
|
wiki_url: self.wiki_url,
|
|
};
|
|
mod_struct.insert(&mut *transaction).await?;
|
|
|
|
for mut version in self.initial_versions {
|
|
version.mod_id = self.mod_id;
|
|
version.insert(&mut *transaction).await?;
|
|
}
|
|
|
|
for category in self.categories {
|
|
sqlx::query!(
|
|
"
|
|
INSERT INTO mods_categories (joining_mod_id, joining_category_id)
|
|
VALUES ($1, $2)
|
|
",
|
|
self.mod_id as ModId,
|
|
category as CategoryId,
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
}
|
|
|
|
Ok(self.mod_id)
|
|
}
|
|
}
|
|
|
|
pub struct Mod {
|
|
pub id: ModId,
|
|
pub team_id: TeamId,
|
|
pub title: String,
|
|
pub description: String,
|
|
pub body_url: String,
|
|
pub published: chrono::DateTime<chrono::Utc>,
|
|
pub updated: chrono::DateTime<chrono::Utc>,
|
|
pub status: StatusId,
|
|
pub downloads: i32,
|
|
pub icon_url: Option<String>,
|
|
pub issues_url: Option<String>,
|
|
pub source_url: Option<String>,
|
|
pub wiki_url: Option<String>,
|
|
}
|
|
|
|
impl Mod {
|
|
pub async fn insert(
|
|
&self,
|
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
) -> Result<(), sqlx::error::Error> {
|
|
sqlx::query!(
|
|
"
|
|
INSERT INTO mods (
|
|
id, team_id, title, description, body_url,
|
|
published, downloads, icon_url, issues_url,
|
|
source_url, wiki_url, status
|
|
)
|
|
VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8, $9,
|
|
$10, $11, $12
|
|
)
|
|
",
|
|
self.id as ModId,
|
|
self.team_id as TeamId,
|
|
&self.title,
|
|
&self.description,
|
|
&self.body_url,
|
|
self.published,
|
|
self.downloads,
|
|
self.icon_url.as_ref(),
|
|
self.issues_url.as_ref(),
|
|
self.source_url.as_ref(),
|
|
self.wiki_url.as_ref(),
|
|
self.status.0
|
|
)
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get<'a, 'b, E>(id: ModId, executor: E) -> Result<Option<Self>, sqlx::error::Error>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
{
|
|
let result = sqlx::query!(
|
|
"
|
|
SELECT title, description, downloads,
|
|
icon_url, body_url, published,
|
|
updated, status,
|
|
issues_url, source_url, wiki_url,
|
|
team_id
|
|
FROM mods
|
|
WHERE id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.fetch_optional(executor)
|
|
.await?;
|
|
|
|
if let Some(row) = result {
|
|
Ok(Some(Mod {
|
|
id,
|
|
team_id: TeamId(row.team_id),
|
|
title: row.title,
|
|
description: row.description,
|
|
downloads: row.downloads,
|
|
body_url: row.body_url,
|
|
icon_url: row.icon_url,
|
|
published: row.published,
|
|
updated: row.updated,
|
|
issues_url: row.issues_url,
|
|
source_url: row.source_url,
|
|
wiki_url: row.wiki_url,
|
|
status: StatusId(row.status),
|
|
}))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn get_many<'a, E>(mod_ids: Vec<ModId>, exec: E) -> Result<Vec<Mod>, sqlx::Error>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
use futures::stream::TryStreamExt;
|
|
|
|
let mod_ids_parsed: Vec<i64> = mod_ids.into_iter().map(|x| x.0).collect();
|
|
let mods = sqlx::query!(
|
|
"
|
|
SELECT id, title, description, downloads,
|
|
icon_url, body_url, published,
|
|
updated, status,
|
|
issues_url, source_url, wiki_url,
|
|
team_id
|
|
FROM mods
|
|
WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))
|
|
",
|
|
&mod_ids_parsed
|
|
)
|
|
.fetch_many(exec)
|
|
.try_filter_map(|e| async {
|
|
Ok(e.right().map(|m| Mod {
|
|
id: ModId(m.id),
|
|
team_id: TeamId(m.team_id),
|
|
title: m.title,
|
|
description: m.description,
|
|
downloads: m.downloads,
|
|
body_url: m.body_url,
|
|
icon_url: m.icon_url,
|
|
published: m.published,
|
|
updated: m.updated,
|
|
issues_url: m.issues_url,
|
|
source_url: m.source_url,
|
|
wiki_url: m.wiki_url,
|
|
status: StatusId(m.status),
|
|
}))
|
|
})
|
|
.try_collect::<Vec<Mod>>()
|
|
.await?;
|
|
|
|
Ok(mods)
|
|
}
|
|
|
|
pub async fn remove_full<'a, 'b, E>(
|
|
id: ModId,
|
|
exec: E,
|
|
) -> Result<Option<()>, sqlx::error::Error>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let result = sqlx::query!(
|
|
"
|
|
SELECT team_id FROM mods WHERE id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.fetch_optional(exec)
|
|
.await?;
|
|
|
|
let team_id: TeamId = if let Some(id) = result {
|
|
TeamId(id.team_id)
|
|
} else {
|
|
return Ok(None);
|
|
};
|
|
|
|
sqlx::query!(
|
|
"
|
|
DELETE FROM mods_categories
|
|
WHERE joining_mod_id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.execute(exec)
|
|
.await?;
|
|
|
|
use futures::TryStreamExt;
|
|
let versions: Vec<VersionId> = sqlx::query!(
|
|
"
|
|
SELECT id FROM versions
|
|
WHERE mod_id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.fetch_many(exec)
|
|
.try_filter_map(|e| async { Ok(e.right().map(|c| VersionId(c.id))) })
|
|
.try_collect::<Vec<VersionId>>()
|
|
.await?;
|
|
|
|
for version in versions {
|
|
super::Version::remove_full(version, exec).await?;
|
|
}
|
|
|
|
sqlx::query!(
|
|
"
|
|
DELETE FROM mods
|
|
WHERE id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.execute(exec)
|
|
.await?;
|
|
|
|
sqlx::query!(
|
|
"
|
|
DELETE FROM team_members
|
|
WHERE team_id = $1
|
|
",
|
|
team_id as TeamId,
|
|
)
|
|
.execute(exec)
|
|
.await?;
|
|
|
|
sqlx::query!(
|
|
"
|
|
DELETE FROM teams
|
|
WHERE id = $1
|
|
",
|
|
team_id as TeamId,
|
|
)
|
|
.execute(exec)
|
|
.await?;
|
|
|
|
Ok(Some(()))
|
|
}
|
|
|
|
pub async fn get_full<'a, 'b, E>(
|
|
id: ModId,
|
|
executor: E,
|
|
) -> Result<Option<QueryMod>, sqlx::error::Error>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let result = Self::get(id, executor).await?;
|
|
if let Some(inner) = result {
|
|
use futures::TryStreamExt;
|
|
let categories: Vec<String> = sqlx::query!(
|
|
"
|
|
SELECT category FROM mods_categories
|
|
INNER JOIN categories ON joining_category_id = id
|
|
WHERE joining_mod_id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.fetch_many(executor)
|
|
.try_filter_map(|e| async { Ok(e.right().map(|c| c.category)) })
|
|
.try_collect::<Vec<String>>()
|
|
.await?;
|
|
|
|
let versions: Vec<VersionId> = sqlx::query!(
|
|
"
|
|
SELECT id FROM versions
|
|
WHERE mod_id = $1
|
|
",
|
|
id as ModId,
|
|
)
|
|
.fetch_many(executor)
|
|
.try_filter_map(|e| async { Ok(e.right().map(|c| VersionId(c.id))) })
|
|
.try_collect::<Vec<VersionId>>()
|
|
.await?;
|
|
|
|
let status = sqlx::query!(
|
|
"
|
|
SELECT status FROM statuses
|
|
WHERE id = $1
|
|
",
|
|
inner.status.0,
|
|
)
|
|
.fetch_one(executor)
|
|
.await?
|
|
.status;
|
|
|
|
Ok(Some(QueryMod {
|
|
inner,
|
|
categories,
|
|
versions,
|
|
status: crate::models::mods::ModStatus::from_str(&status),
|
|
}))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn get_many_full<'a, E>(
|
|
mod_ids: Vec<ModId>,
|
|
exec: E,
|
|
) -> Result<Vec<Option<QueryMod>>, sqlx::Error>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
// TODO: this could be optimized
|
|
futures::future::try_join_all(mod_ids.into_iter().map(|id| Self::get_full(id, exec))).await
|
|
}
|
|
}
|
|
|
|
pub struct QueryMod {
|
|
pub inner: Mod,
|
|
|
|
pub categories: Vec<String>,
|
|
pub versions: Vec<VersionId>,
|
|
pub status: crate::models::mods::ModStatus,
|
|
}
|