You've already forked AstralRinth
forked from didirus/AstralRinth
Implement more database methods and basic API routes (#50)
* feat: Implement more database methods & add mod and version routes * feat: Implement deleting mods/versions & implement categories * feat: Implement routes for categories, game versions & loaders * feat: Reorganize API routes in a (hopefully) usable way
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use super::categories::{GameVersion, Loader};
|
||||
use super::ids::*;
|
||||
use super::DatabaseError;
|
||||
|
||||
@@ -173,7 +174,118 @@ impl Version {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_dependencies<'a, E>(&self, exec: E) -> Result<Vec<VersionId>, sqlx::Error>
|
||||
// TODO: someone verify this
|
||||
pub async fn remove_full<'a, E>(id: VersionId, exec: E) -> Result<Option<()>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
use sqlx::Done;
|
||||
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
if !result.exists.unwrap_or(false) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM game_versions_versions gvv
|
||||
WHERE gvv.joining_version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM loaders_versions
|
||||
WHERE loaders_versions.version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
let mut files = sqlx::query!(
|
||||
"
|
||||
SELECT files.id, files.url, files.filename FROM files
|
||||
WHERE files.version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|c| VersionFile {
|
||||
id: FileId(c.id),
|
||||
version_id: id,
|
||||
url: c.url,
|
||||
filename: c.filename,
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<VersionFile>>()
|
||||
.await?;
|
||||
|
||||
for file in files {
|
||||
// TODO: store backblaze id in database so that we can delete the files here
|
||||
// For now, we can't delete the files since we don't have the backblaze id
|
||||
log::warn!(
|
||||
"Can't delete version file id: {} (url: {}, name: {})",
|
||||
file.id.0,
|
||||
file.url,
|
||||
file.filename
|
||||
)
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM hashes
|
||||
WHERE EXISTS(
|
||||
SELECT 1 FROM files WHERE
|
||||
(files.version_id = $1) AND
|
||||
(hashes.file_id = files.id)
|
||||
)
|
||||
",
|
||||
id as VersionId
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM files
|
||||
WHERE files.version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM versions WHERE id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
pub async fn get_dependencies<'a, E>(
|
||||
id: VersionId,
|
||||
exec: E,
|
||||
) -> Result<Vec<VersionId>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
@@ -184,7 +296,7 @@ impl Version {
|
||||
SELECT dependency_id id FROM dependencies
|
||||
WHERE dependent_id = $1
|
||||
",
|
||||
self.id as VersionId,
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|v| VersionId(v.id))) })
|
||||
@@ -193,20 +305,177 @@ impl Version {
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
pub async fn get_mod_versions<'a, E>(
|
||||
mod_id: ModId,
|
||||
exec: E,
|
||||
) -> Result<Vec<VersionId>, sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let vec = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM versions
|
||||
WHERE mod_id = $1
|
||||
",
|
||||
mod_id as ModId,
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|v| VersionId(v.id))) })
|
||||
.try_collect::<Vec<VersionId>>()
|
||||
.await?;
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
pub async fn get<'a, 'b, E>(
|
||||
id: VersionId,
|
||||
executor: E,
|
||||
) -> Result<Option<Self>, sqlx::error::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT v.mod_id, v.name, v.version_number,
|
||||
v.changelog_url, v.date_published, v.downloads,
|
||||
v.release_channel
|
||||
FROM versions v
|
||||
WHERE v.id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = result {
|
||||
Ok(Some(Version {
|
||||
id,
|
||||
mod_id: ModId(row.mod_id),
|
||||
name: row.name,
|
||||
version_number: row.version_number,
|
||||
changelog_url: row.changelog_url,
|
||||
date_published: row.date_published,
|
||||
downloads: row.downloads,
|
||||
release_channel: ChannelId(row.release_channel),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_full<'a, 'b, E>(
|
||||
id: VersionId,
|
||||
executor: E,
|
||||
) -> Result<Option<QueryVersion>, sqlx::error::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT v.mod_id, v.name, v.version_number,
|
||||
v.changelog_url, v.date_published, v.downloads,
|
||||
release_channels.channel
|
||||
FROM versions v
|
||||
INNER JOIN release_channels ON v.release_channel = release_channels.id
|
||||
WHERE v.id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = result {
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::Row;
|
||||
|
||||
let game_versions: Vec<String> = sqlx::query!(
|
||||
"
|
||||
SELECT gv.version FROM game_versions_versions gvv
|
||||
INNER JOIN game_versions gv ON gvv.game_version_id=gv.id
|
||||
WHERE gvv.joining_version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
let loaders: Vec<String> = sqlx::query!(
|
||||
"
|
||||
SELECT loaders.loader FROM loaders
|
||||
INNER JOIN loaders_versions ON loaders.id = loaders_versions.loader_id
|
||||
WHERE loaders_versions.version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| c.loader)) })
|
||||
.try_collect::<Vec<String>>()
|
||||
.await?;
|
||||
|
||||
let mut files = sqlx::query!(
|
||||
"
|
||||
SELECT files.id, files.url, files.filename FROM files
|
||||
WHERE files.version_id = $1
|
||||
",
|
||||
id as VersionId,
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|c| QueryFile {
|
||||
id: FileId(c.id),
|
||||
url: c.url,
|
||||
filename: c.filename,
|
||||
hashes: std::collections::HashMap::new(),
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<QueryFile>>()
|
||||
.await?;
|
||||
|
||||
for file in files.iter_mut() {
|
||||
let mut files = sqlx::query!(
|
||||
"
|
||||
SELECT hashes.algorithm, hashes.hash FROM hashes
|
||||
WHERE hashes.file_id = $1
|
||||
",
|
||||
file.id as FileId
|
||||
)
|
||||
.fetch_many(executor)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| (c.algorithm, c.hash))) })
|
||||
.try_collect::<Vec<(String, Vec<u8>)>>()
|
||||
.await?;
|
||||
|
||||
file.hashes.extend(files);
|
||||
}
|
||||
|
||||
Ok(Some(QueryVersion {
|
||||
id,
|
||||
mod_id: ModId(row.mod_id),
|
||||
name: row.name,
|
||||
version_number: row.version_number,
|
||||
changelog_url: row.changelog_url,
|
||||
date_published: row.date_published,
|
||||
downloads: row.downloads,
|
||||
|
||||
release_channel: row.channel,
|
||||
files: Vec::<QueryFile>::new(),
|
||||
loaders,
|
||||
game_versions,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReleaseChannel {
|
||||
pub id: ChannelId,
|
||||
pub channel: String,
|
||||
}
|
||||
pub struct Loader {
|
||||
pub id: LoaderId,
|
||||
pub loader: String,
|
||||
}
|
||||
pub struct GameVersion {
|
||||
pub id: GameVersionId,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
pub struct VersionFile {
|
||||
pub id: FileId,
|
||||
@@ -221,7 +490,24 @@ pub struct FileHash {
|
||||
pub hash: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Category {
|
||||
pub id: CategoryId,
|
||||
pub category: String,
|
||||
pub struct QueryVersion {
|
||||
pub id: VersionId,
|
||||
pub mod_id: ModId,
|
||||
pub name: String,
|
||||
pub version_number: String,
|
||||
pub changelog_url: Option<String>,
|
||||
pub date_published: chrono::DateTime<chrono::Utc>,
|
||||
pub downloads: i32,
|
||||
|
||||
pub release_channel: String,
|
||||
pub files: Vec<QueryFile>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub loaders: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct QueryFile {
|
||||
pub id: FileId,
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
pub hashes: std::collections::HashMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user