Project Types, Code Cleanup, and Rename Mods -> Projects (#192)

* Initial work for modpacks and project types

* Code cleanup, fix some issues

* Username route getting, remove pointless tests

* Base validator types + fixes

* Fix strange IML generation

* Multiple hash requests for version files

* Fix docker build (hopefully)

* Legacy routes

* Finish validator architecture

* Update rust version in dockerfile

* Added caching and fixed typo (#203)

* Added caching and fixed typo

* Fixed clippy error

* Removed log for cache

* Add final validators, fix how loaders are handled and add icons to tags

* Fix search module

* Fix parts of legacy API not working

Co-authored-by: Redblueflame <contact@redblueflame.com>
This commit is contained in:
Geometrically
2021-05-30 15:02:07 -07:00
committed by GitHub
parent 712424c339
commit 16db28060c
55 changed files with 6656 additions and 3908 deletions

44
src/database/cache/mod.rs vendored Normal file
View File

@@ -0,0 +1,44 @@
//pub mod project_cache;
//pub mod project_query_cache;
#[macro_export]
macro_rules! generate_cache {
($name:ident,$id:ty, $val:ty, $cache_name:ident, $mod_name:ident, $getter_name:ident, $setter_name:ident) => {
pub mod $mod_name {
use cached::async_mutex::Mutex;
use cached::{Cached, SizedCache};
use lazy_static::lazy_static;
lazy_static! {
static ref $cache_name: Mutex<SizedCache<$id, $val>> =
Mutex::new(SizedCache::with_size(400));
}
pub async fn $getter_name<'a>(id: $id) -> Option<$val> {
let mut cache = $cache_name.lock().await;
Cached::cache_get(&mut *cache, &id).map(|e| e.clone())
}
pub async fn $setter_name<'a>(id: $id, val: &$val) {
let mut cache = $cache_name.lock().await;
Cached::cache_set(&mut *cache, id, val.clone());
}
}
};
}
generate_cache!(
project,
String,
crate::database::Project,
PROJECT_CACHE,
project_cache,
get_cache_project,
set_cache_project
);
generate_cache!(
query_project,
String,
crate::database::models::project_item::QueryProject,
QUERY_PROJECT_CACHE,
query_project_cache,
get_cache_query_project,
set_cache_query_project
);

View File

@@ -1,7 +1,7 @@
mod cache;
pub mod models;
mod postgres_database;
pub use models::Mod;
pub use models::Project;
pub use models::Version;
pub use postgres_database::check_for_migrations;
pub use postgres_database::connect;

View File

@@ -2,19 +2,30 @@ use super::ids::*;
use super::DatabaseError;
use futures::TryStreamExt;
pub struct ProjectType {
pub id: ProjectTypeId,
pub name: String,
}
pub struct Loader {
pub id: LoaderId,
pub loader: String,
pub icon: String,
pub supported_project_types: Vec<String>,
}
pub struct GameVersion {
pub id: GameVersionId,
pub version: String,
pub version_type: String,
pub date: chrono::DateTime<chrono::Utc>,
}
pub struct Category {
pub id: CategoryId,
pub category: String,
pub project_type: String,
pub icon: String,
}
pub struct ReportType {
@@ -36,11 +47,17 @@ pub struct DonationPlatform {
pub struct CategoryBuilder<'a> {
pub name: Option<&'a str>,
pub project_type: Option<&'a ProjectTypeId>,
pub icon: Option<&'a str>,
}
impl Category {
pub fn builder() -> CategoryBuilder<'static> {
CategoryBuilder { name: None }
CategoryBuilder {
name: None,
project_type: None,
icon: None,
}
}
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<CategoryId>, DatabaseError>
@@ -59,7 +76,36 @@ impl Category {
SELECT id FROM categories
WHERE category = $1
",
name
name,
)
.fetch_optional(exec)
.await?;
Ok(result.map(|r| CategoryId(r.id)))
}
pub async fn get_id_project<'a, E>(
name: &str,
project_type: ProjectTypeId,
exec: E,
) -> Result<Option<CategoryId>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(DatabaseError::InvalidIdentifier(name.to_string()));
}
let result = sqlx::query!(
"
SELECT id FROM categories
WHERE category = $1 AND project_type = $2
",
name,
project_type as ProjectTypeId
)
.fetch_optional(exec)
.await?;
@@ -84,18 +130,27 @@ impl Category {
Ok(result.category)
}
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
pub async fn list<'a, E>(exec: E) -> Result<Vec<Category>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT category FROM categories
SELECT c.id id, c.category category, c.icon icon, pt.name project_type
FROM categories c
INNER JOIN project_types pt ON c.project_type = pt.id
"
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.category)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async {
Ok(e.right().map(|c| Category {
id: CategoryId(c.id),
category: c.category,
project_type: c.project_type,
icon: c.icon,
}))
})
.try_collect::<Vec<Category>>()
.await?;
Ok(result)
@@ -133,24 +188,49 @@ impl<'a> CategoryBuilder<'a> {
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
Ok(Self { name: Some(name) })
Ok(Self {
name: Some(name),
..self
})
} else {
Err(DatabaseError::InvalidIdentifier(name.to_string()))
}
}
pub fn project_type(
self,
project_type: &'a ProjectTypeId,
) -> Result<CategoryBuilder<'a>, DatabaseError> {
Ok(Self {
project_type: Some(project_type),
..self
})
}
pub fn icon(self, icon: &'a str) -> Result<CategoryBuilder<'a>, DatabaseError> {
Ok(Self {
icon: Some(icon),
..self
})
}
pub async fn insert<'b, E>(self, exec: E) -> Result<CategoryId, DatabaseError>
where
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
{
let id = *self
.project_type
.ok_or_else(|| DatabaseError::Other("No project type specified.".to_string()))?;
let result = sqlx::query!(
"
INSERT INTO categories (category)
VALUES ($1)
ON CONFLICT (category) DO NOTHING
INSERT INTO categories (category, project_type, icon)
VALUES ($1, $2, $3)
ON CONFLICT (category, project_type, icon) DO NOTHING
RETURNING id
",
self.name
self.name,
id as ProjectTypeId,
self.icon
)
.fetch_one(exec)
.await?;
@@ -161,11 +241,17 @@ impl<'a> CategoryBuilder<'a> {
pub struct LoaderBuilder<'a> {
pub name: Option<&'a str>,
pub icon: Option<&'a str>,
pub supported_project_types: Option<&'a [ProjectTypeId]>,
}
impl Loader {
pub fn builder() -> LoaderBuilder<'static> {
LoaderBuilder { name: None }
LoaderBuilder {
name: None,
icon: None,
supported_project_types: None,
}
}
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<LoaderId>, DatabaseError>
@@ -209,24 +295,41 @@ impl Loader {
Ok(result.loader)
}
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
pub async fn list<'a, E>(exec: E) -> Result<Vec<Loader>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT loader FROM loaders
SELECT l.id id, l.loader loader, l.icon icon,
STRING_AGG(DISTINCT pt.name, ',') project_types
FROM loaders l
LEFT OUTER JOIN loaders_project_types lpt ON joining_loader_id = l.id
LEFT OUTER JOIN project_types pt ON lpt.joining_project_type_id = pt.id
GROUP BY l.id;
"
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.loader)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async {
Ok(e.right().map(|x| Loader {
id: LoaderId(x.id),
loader: x.loader,
icon: x.icon,
supported_project_types: x
.project_types
.unwrap_or_default()
.split(',')
.map(|x| x.to_string())
.collect(),
}))
})
.try_collect::<Vec<_>>()
.await?;
Ok(result)
}
// TODO: remove loaders with mods using them
// TODO: remove loaders with projects using them
pub async fn remove<'a, E>(name: &str, exec: E) -> Result<Option<()>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
@@ -259,28 +362,74 @@ impl<'a> LoaderBuilder<'a> {
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
Ok(Self { name: Some(name) })
Ok(Self {
name: Some(name),
..self
})
} else {
Err(DatabaseError::InvalidIdentifier(name.to_string()))
}
}
pub async fn insert<'b, E>(self, exec: E) -> Result<LoaderId, DatabaseError>
where
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
{
pub fn icon(self, icon: &'a str) -> Result<LoaderBuilder<'a>, DatabaseError> {
Ok(Self {
icon: Some(icon),
..self
})
}
pub fn supported_project_types(
self,
supported_project_types: &'a [ProjectTypeId],
) -> Result<LoaderBuilder<'a>, DatabaseError> {
Ok(Self {
supported_project_types: Some(supported_project_types),
..self
})
}
pub async fn insert(
self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<LoaderId, super::DatabaseError> {
let result = sqlx::query!(
"
INSERT INTO loaders (loader)
VALUES ($1)
ON CONFLICT (loader) DO NOTHING
INSERT INTO loaders (loader, icon)
VALUES ($1, $2)
ON CONFLICT (loader, icon) DO NOTHING
RETURNING id
",
self.name
self.name,
self.icon
)
.fetch_one(exec)
.fetch_one(&mut *transaction)
.await?;
if let Some(project_types) = self.supported_project_types {
sqlx::query!(
"
DELETE FROM loaders_project_types
WHERE joining_loader_id = $1
",
result.id
)
.execute(&mut *transaction)
.await?;
for project_type in project_types {
sqlx::query!(
"
INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id)
VALUES ($1, $2)
",
result.id,
project_type.0,
)
.execute(&mut *transaction)
.await?;
}
}
Ok(LoaderId(result.id))
}
}
@@ -341,19 +490,24 @@ impl GameVersion {
Ok(result.version)
}
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
pub async fn list<'a, E>(exec: E) -> Result<Vec<GameVersion>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT version FROM game_versions
SELECT gv.id id, gv.version version_, gv.type type_, gv.created created FROM game_versions gv
ORDER BY created DESC
"
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async { Ok(e.right().map(|c| GameVersion {
id: GameVersionId(c.id),
version: c.version_,
version_type: c.type_,
date: c.created
})) })
.try_collect::<Vec<GameVersion>>()
.await?;
Ok(result)
@@ -363,7 +517,7 @@ impl GameVersion {
version_type_option: Option<&str>,
major_option: Option<bool>,
exec: E,
) -> Result<Vec<String>, DatabaseError>
) -> Result<Vec<GameVersion>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
@@ -373,7 +527,7 @@ impl GameVersion {
if let Some(major) = major_option {
result = sqlx::query!(
"
SELECT version FROM game_versions
SELECT gv.id id, gv.version version_, gv.type type_, gv.created created FROM game_versions gv
WHERE major = $1 AND type = $2
ORDER BY created DESC
",
@@ -381,35 +535,50 @@ impl GameVersion {
version_type
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async { Ok(e.right().map(|c| GameVersion {
id: GameVersionId(c.id),
version: c.version_,
version_type: c.type_,
date: c.created
})) })
.try_collect::<Vec<GameVersion>>()
.await?;
} else {
result = sqlx::query!(
"
SELECT version FROM game_versions
SELECT gv.id id, gv.version version_, gv.type type_, gv.created created FROM game_versions gv
WHERE type = $1
ORDER BY created DESC
",
version_type
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async { Ok(e.right().map(|c| GameVersion {
id: GameVersionId(c.id),
version: c.version_,
version_type: c.type_,
date: c.created
})) })
.try_collect::<Vec<GameVersion>>()
.await?;
}
} else if let Some(major) = major_option {
result = sqlx::query!(
"
SELECT version FROM game_versions
SELECT gv.id id, gv.version version_, gv.type type_, gv.created created FROM game_versions gv
WHERE major = $1
ORDER BY created DESC
",
major
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
.try_collect::<Vec<String>>()
.try_filter_map(|e| async { Ok(e.right().map(|c| GameVersion {
id: GameVersionId(c.id),
version: c.version_,
version_type: c.type_,
date: c.created
})) })
.try_collect::<Vec<GameVersion>>()
.await?;
} else {
result = Vec::new();
@@ -867,7 +1036,6 @@ impl ReportType {
Ok(result)
}
// TODO: remove loaders with mods using them
pub async fn remove<'a, E>(name: &str, exec: E) -> Result<Option<()>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
@@ -925,3 +1093,156 @@ impl<'a> ReportTypeBuilder<'a> {
Ok(ReportTypeId(result.id))
}
}
pub struct ProjectTypeBuilder<'a> {
pub name: Option<&'a str>,
}
impl ProjectType {
pub fn builder() -> ProjectTypeBuilder<'static> {
ProjectTypeBuilder { name: None }
}
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<ProjectTypeId>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(DatabaseError::InvalidIdentifier(name.to_string()));
}
let result = sqlx::query!(
"
SELECT id FROM project_types
WHERE name = $1
",
name
)
.fetch_optional(exec)
.await?;
Ok(result.map(|r| ProjectTypeId(r.id)))
}
pub async fn get_many_id<'a, E>(
names: &Vec<String>,
exec: E,
) -> Result<Vec<ProjectType>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let project_types = sqlx::query!(
"
SELECT id, name FROM project_types
WHERE name IN (SELECT * FROM UNNEST($1::varchar[]))
",
names
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|x| ProjectType {
id: ProjectTypeId(x.id),
name: x.name,
}))
})
.try_collect::<Vec<ProjectType>>()
.await?;
Ok(project_types)
}
pub async fn get_name<'a, E>(id: ProjectTypeId, exec: E) -> Result<String, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT name FROM project_types
WHERE id = $1
",
id as ProjectTypeId
)
.fetch_one(exec)
.await?;
Ok(result.name)
}
pub async fn list<'a, E>(exec: E) -> Result<Vec<String>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT name FROM project_types
"
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| c.name)) })
.try_collect::<Vec<String>>()
.await?;
Ok(result)
}
// TODO: remove loaders with mods using them
pub async fn remove<'a, E>(name: &str, exec: E) -> Result<Option<()>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
use sqlx::Done;
let result = sqlx::query!(
"
DELETE FROM project_types
WHERE name = $1
",
name
)
.execute(exec)
.await?;
if result.rows_affected() == 0 {
// Nothing was deleted
Ok(None)
} else {
Ok(Some(()))
}
}
}
impl<'a> ProjectTypeBuilder<'a> {
/// The name of the project type. Must be ASCII alphanumeric or `-`/`_`
pub fn name(self, name: &'a str) -> Result<ProjectTypeBuilder<'a>, DatabaseError> {
if name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
Ok(Self { name: Some(name) })
} else {
Err(DatabaseError::InvalidIdentifier(name.to_string()))
}
}
pub async fn insert<'b, E>(self, exec: E) -> Result<ProjectTypeId, DatabaseError>
where
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
INSERT INTO project_types (name)
VALUES ($1)
ON CONFLICT (name) DO NOTHING
RETURNING id
",
self.name
)
.fetch_one(exec)
.await?;
Ok(ProjectTypeId(result.id))
}
}

View File

@@ -38,11 +38,11 @@ macro_rules! generate_ids {
}
generate_ids!(
pub generate_mod_id,
ModId,
pub generate_project_id,
ProjectId,
8,
"SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)",
ModId
ProjectId
);
generate_ids!(
pub generate_version_id,
@@ -115,7 +115,11 @@ pub struct TeamMemberId(pub i64);
#[derive(Copy, Clone, Debug, Type)]
#[sqlx(transparent)]
pub struct ModId(pub i64);
pub struct ProjectId(pub i64);
#[derive(Copy, Clone, Debug, Type)]
#[sqlx(transparent)]
pub struct ProjectTypeId(pub i32);
#[derive(Copy, Clone, Debug, Type)]
#[sqlx(transparent)]
pub struct StatusId(pub i32);
@@ -169,14 +173,14 @@ pub struct NotificationActionId(pub i32);
use crate::models::ids;
impl From<ids::ModId> for ModId {
fn from(id: ids::ModId) -> Self {
ModId(id.0 as i64)
impl From<ids::ProjectId> for ProjectId {
fn from(id: ids::ProjectId) -> Self {
ProjectId(id.0 as i64)
}
}
impl From<ModId> for ids::ModId {
fn from(id: ModId) -> Self {
ids::ModId(id.0 as u64)
impl From<ProjectId> for ids::ProjectId {
fn from(id: ProjectId) -> Self {
ids::ProjectId(id.0 as u64)
}
}
impl From<ids::UserId> for UserId {

View File

@@ -5,15 +5,15 @@ use thiserror::Error;
pub mod categories;
pub mod ids;
pub mod mod_item;
pub mod notification_item;
pub mod project_item;
pub mod report_item;
pub mod team_item;
pub mod user_item;
pub mod version_item;
pub use ids::*;
pub use mod_item::Mod;
pub use project_item::Project;
pub use team_item::Team;
pub use team_item::TeamMember;
pub use user_item::User;
@@ -62,7 +62,7 @@ impl ids::ChannelId {
impl ids::StatusId {
pub async fn get_id<'a, E>(
status: &crate::models::mods::ModStatus,
status: &crate::models::projects::ProjectStatus,
exec: E,
) -> Result<Option<Self>, DatabaseError>
where
@@ -84,7 +84,7 @@ impl ids::StatusId {
impl ids::SideTypeId {
pub async fn get_id<'a, E>(
side: &crate::models::mods::SideType,
side: &crate::models::projects::SideType,
exec: E,
) -> Result<Option<Self>, DatabaseError>
where
@@ -122,3 +122,22 @@ impl ids::DonationPlatformId {
Ok(result.map(|r| ids::DonationPlatformId(r.id)))
}
}
impl ids::ProjectTypeId {
pub async fn get_id<'a, E>(project_type: String, exec: E) -> Result<Option<Self>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT id FROM project_types
WHERE name = $1
",
project_type
)
.fetch_optional(exec)
.await?;
Ok(result.map(|r| ids::ProjectTypeId(r.id)))
}
}

View File

@@ -179,7 +179,8 @@ impl Notification {
FROM notifications n
LEFT OUTER JOIN notifications_actions na on n.id = na.notification_id
WHERE n.id IN (SELECT * FROM UNNEST($1::bigint[]))
GROUP BY n.id, n.user_id;
GROUP BY n.id, n.user_id
ORDER BY n.created DESC;
",
&notification_ids_parsed
)

View File

@@ -1,7 +1,11 @@
use super::ids::*;
use crate::database::cache::project_cache::{get_cache_project, set_cache_project};
use crate::database::cache::query_project_cache::{
get_cache_query_project, set_cache_query_project,
};
#[derive(Clone, Debug)]
pub struct DonationUrl {
pub mod_id: ModId,
pub project_id: ProjectId,
pub platform_id: DonationPlatformId,
pub platform_short: String,
pub platform_name: String,
@@ -22,7 +26,7 @@ impl DonationUrl {
$1, $2, $3
)
",
self.mod_id as ModId,
self.project_id as ProjectId,
self.platform_id as DonationPlatformId,
self.url,
)
@@ -33,8 +37,9 @@ impl DonationUrl {
}
}
pub struct ModBuilder {
pub mod_id: ModId,
pub struct ProjectBuilder {
pub project_id: ProjectId,
pub project_type_id: ProjectTypeId,
pub team_id: TeamId,
pub title: String,
pub description: String,
@@ -55,13 +60,14 @@ pub struct ModBuilder {
pub donation_urls: Vec<DonationUrl>,
}
impl ModBuilder {
impl ProjectBuilder {
pub async fn insert(
self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<ModId, super::DatabaseError> {
let mod_struct = Mod {
id: self.mod_id,
) -> Result<ProjectId, super::DatabaseError> {
let project_struct = Project {
id: self.project_id,
project_type: self.project_type_id,
team_id: self.team_id,
title: self.title,
description: self.description,
@@ -83,15 +89,15 @@ impl ModBuilder {
license: self.license,
slug: self.slug,
};
mod_struct.insert(&mut *transaction).await?;
project_struct.insert(&mut *transaction).await?;
for mut version in self.initial_versions {
version.mod_id = self.mod_id;
version.project_id = self.project_id;
version.insert(&mut *transaction).await?;
}
for mut donation in self.donation_urls {
donation.mod_id = self.mod_id;
donation.project_id = self.project_id;
donation.insert(&mut *transaction).await?;
}
@@ -101,19 +107,20 @@ impl ModBuilder {
INSERT INTO mods_categories (joining_mod_id, joining_category_id)
VALUES ($1, $2)
",
self.mod_id as ModId,
self.project_id as ProjectId,
category as CategoryId,
)
.execute(&mut *transaction)
.await?;
}
Ok(self.mod_id)
Ok(self.project_id)
}
}
pub struct Mod {
pub id: ModId,
#[derive(Clone, Debug)]
pub struct Project {
pub id: ProjectId,
pub project_type: ProjectTypeId,
pub team_id: TeamId,
pub title: String,
pub description: String,
@@ -136,7 +143,7 @@ pub struct Mod {
pub slug: Option<String>,
}
impl Mod {
impl Project {
pub async fn insert(
&self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
@@ -148,17 +155,17 @@ impl Mod {
published, downloads, icon_url, issues_url,
source_url, wiki_url, status, discord_url,
client_side, server_side, license_url, license,
slug
slug, project_type
)
VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13,
$14, $15, $16, $17,
LOWER($18)
LOWER($18), $19
)
",
self.id as ModId,
self.id as ProjectId,
self.team_id as TeamId,
&self.title,
&self.description,
@@ -175,7 +182,8 @@ impl Mod {
self.server_side as SideTypeId,
self.license_url.as_ref(),
self.license as LicenseId,
self.slug.as_ref()
self.slug.as_ref(),
self.project_type as ProjectTypeId
)
.execute(&mut *transaction)
.await?;
@@ -183,13 +191,16 @@ impl Mod {
Ok(())
}
pub async fn get<'a, 'b, E>(id: ModId, executor: E) -> Result<Option<Self>, sqlx::error::Error>
pub async fn get<'a, 'b, E>(
id: ProjectId,
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT title, description, downloads, follows,
SELECT project_type, title, description, downloads, follows,
icon_url, body, body_url, published,
updated, status,
issues_url, source_url, wiki_url, discord_url, license_url,
@@ -197,14 +208,15 @@ impl Mod {
FROM mods
WHERE id = $1
",
id as ModId,
id as ProjectId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(Mod {
Ok(Some(Project {
id,
project_type: ProjectTypeId(row.project_type),
team_id: TeamId(row.team_id),
title: row.title,
description: row.description,
@@ -231,16 +243,19 @@ impl Mod {
}
}
pub async fn get_many<'a, E>(mod_ids: Vec<ModId>, exec: E) -> Result<Vec<Mod>, sqlx::Error>
pub async fn get_many<'a, E>(
project_ids: Vec<ProjectId>,
exec: E,
) -> Result<Vec<Project>, 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!(
let project_ids_parsed: Vec<i64> = project_ids.into_iter().map(|x| x.0).collect();
let projects = sqlx::query!(
"
SELECT id, title, description, downloads, follows,
SELECT id, project_type, title, description, downloads, follows,
icon_url, body, body_url, published,
updated, status,
issues_url, source_url, wiki_url, discord_url, license_url,
@@ -248,12 +263,13 @@ impl Mod {
FROM mods
WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))
",
&mod_ids_parsed
&project_ids_parsed
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|m| Mod {
id: ModId(m.id),
Ok(e.right().map(|m| Project {
id: ProjectId(m.id),
project_type: ProjectTypeId(m.project_type),
team_id: TeamId(m.team_id),
title: m.title,
description: m.description,
@@ -276,14 +292,14 @@ impl Mod {
follows: m.follows,
}))
})
.try_collect::<Vec<Mod>>()
.try_collect::<Vec<Project>>()
.await?;
Ok(mods)
Ok(projects)
}
pub async fn remove_full<'a, 'b, E>(
id: ModId,
id: ProjectId,
exec: E,
) -> Result<Option<()>, sqlx::error::Error>
where
@@ -293,7 +309,7 @@ impl Mod {
"
SELECT team_id FROM mods WHERE id = $1
",
id as ModId,
id as ProjectId,
)
.fetch_optional(exec)
.await?;
@@ -309,7 +325,7 @@ impl Mod {
DELETE FROM mod_follows
WHERE mod_id = $1
",
id as ModId
id as ProjectId
)
.execute(exec)
.await?;
@@ -319,7 +335,7 @@ impl Mod {
DELETE FROM mod_follows
WHERE mod_id = $1
",
id as ModId,
id as ProjectId,
)
.execute(exec)
.await?;
@@ -329,7 +345,7 @@ impl Mod {
DELETE FROM reports
WHERE mod_id = $1
",
id as ModId,
id as ProjectId,
)
.execute(exec)
.await?;
@@ -339,7 +355,7 @@ impl Mod {
DELETE FROM mods_categories
WHERE joining_mod_id = $1
",
id as ModId,
id as ProjectId,
)
.execute(exec)
.await?;
@@ -349,7 +365,7 @@ impl Mod {
DELETE FROM mods_donations
WHERE joining_mod_id = $1
",
id as ModId,
id as ProjectId,
)
.execute(exec)
.await?;
@@ -360,7 +376,7 @@ impl Mod {
SELECT id FROM versions
WHERE mod_id = $1
",
id as ModId,
id as ProjectId,
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|c| VersionId(c.id))) })
@@ -376,7 +392,7 @@ impl Mod {
DELETE FROM mods
WHERE id = $1
",
id as ModId,
id as ProjectId,
)
.execute(exec)
.await?;
@@ -407,7 +423,7 @@ impl Mod {
pub async fn get_full_from_slug<'a, 'b, E>(
slug: &str,
executor: E,
) -> Result<Option<QueryMod>, sqlx::error::Error>
) -> Result<Option<QueryProject>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
@@ -421,49 +437,154 @@ impl Mod {
.fetch_optional(executor)
.await?;
if let Some(mod_id) = id {
Mod::get_full(ModId(mod_id.id), executor).await
if let Some(project_id) = id {
Project::get_full(ProjectId(project_id.id), executor).await
} else {
Ok(None)
}
}
pub async fn get_full<'a, 'b, E>(
id: ModId,
pub async fn get_from_slug<'a, 'b, E>(
slug: &str,
executor: E,
) -> Result<Option<QueryMod>, sqlx::error::Error>
) -> Result<Option<Project>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let id = sqlx::query!(
"
SELECT id FROM mods
WHERE LOWER(slug) = LOWER($1)
",
slug
)
.fetch_optional(executor)
.await?;
if let Some(project_id) = id {
Project::get(ProjectId(project_id.id), executor).await
} else {
Ok(None)
}
}
pub async fn get_from_slug_or_project_id<'a, 'b, E>(
slug_or_project_id: String,
executor: E,
) -> Result<Option<Project>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
// Check in the cache
let cached = get_cache_project(slug_or_project_id.clone()).await;
if let Some(data) = cached {
return Ok(Some(data));
}
let id_option =
crate::models::ids::base62_impl::parse_base62(&*slug_or_project_id.clone()).ok();
if let Some(id) = id_option {
let mut project = Project::get(ProjectId(id as i64), executor).await?;
if project.is_none() {
project = Project::get_from_slug(&slug_or_project_id, executor).await?;
}
// Cache the response
if let Some(data) = project {
set_cache_project(slug_or_project_id.clone(), &data).await;
Ok(Some(data))
} else {
Ok(None)
}
} else {
let project = Project::get_from_slug(&slug_or_project_id, executor).await?;
// Capture the data, and try to cache it
if let Some(data) = project {
set_cache_project(slug_or_project_id.clone(), &data).await;
Ok(Some(data))
} else {
Ok(None)
}
}
}
pub async fn get_full_from_slug_or_project_id<'a, 'b, E>(
slug_or_project_id: String,
executor: E,
) -> Result<Option<QueryProject>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
// Query cache
let cached = get_cache_query_project(slug_or_project_id.clone()).await;
if let Some(data) = cached {
return Ok(Some(data));
}
let id_option =
crate::models::ids::base62_impl::parse_base62(&*slug_or_project_id.clone()).ok();
if let Some(id) = id_option {
let mut project = Project::get_full(ProjectId(id as i64), executor).await?;
if project.is_none() {
project = Project::get_full_from_slug(&slug_or_project_id, executor).await?;
}
// Save the variable
if let Some(data) = project {
set_cache_query_project(slug_or_project_id.clone(), &data).await;
Ok(Some(data))
} else {
Ok(None)
}
} else {
let project = Project::get_full_from_slug(&slug_or_project_id, executor).await?;
if let Some(data) = project {
set_cache_query_project(slug_or_project_id.clone(), &data).await;
Ok(Some(data))
} else {
Ok(None)
}
}
}
pub async fn get_full<'a, 'b, E>(
id: ProjectId,
executor: E,
) -> Result<Option<QueryProject>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT m.id id, m.title title, m.description description, m.downloads downloads, m.follows follows,
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
m.updated updated, m.status status,
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug,
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name,
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions
FROM mods m
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id
LEFT OUTER JOIN versions v ON v.mod_id = m.id
INNER JOIN project_types pt ON pt.id = m.project_type
INNER JOIN statuses s ON s.id = m.status
INNER JOIN side_types cs ON m.client_side = cs.id
INNER JOIN side_types ss ON m.server_side = ss.id
INNER JOIN licenses l ON m.license = l.id
WHERE m.id = $1
GROUP BY m.id, s.id, cs.id, ss.id, l.id;
GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;
",
id as ModId,
id as ProjectId,
)
.fetch_optional(executor)
.await?;
if let Some(m) = result {
Ok(Some(QueryMod {
inner: Mod {
id: ModId(m.id),
Ok(Some(QueryProject {
inner: Project {
id: ProjectId(m.id),
project_type: ProjectTypeId(m.project_type),
team_id: TeamId(m.team_id),
title: m.title.clone(),
description: m.description.clone(),
@@ -485,6 +606,7 @@ impl Mod {
body: m.body.clone(),
follows: m.follows,
},
project_type: m.project_type_name,
categories: m
.categories
.unwrap_or_default()
@@ -498,11 +620,11 @@ impl Mod {
.map(|x| VersionId(x.parse().unwrap_or_default()))
.collect(),
donation_urls: vec![],
status: crate::models::mods::ModStatus::from_str(&m.status_name),
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
license_id: m.short,
license_name: m.license_name,
client_side: crate::models::mods::SideType::from_str(&m.client_side_type),
server_side: crate::models::mods::SideType::from_str(&m.server_side_type),
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
}))
} else {
Ok(None)
@@ -510,42 +632,44 @@ impl Mod {
}
pub async fn get_many_full<'a, E>(
mod_ids: Vec<ModId>,
project_ids: Vec<ProjectId>,
exec: E,
) -> Result<Vec<QueryMod>, sqlx::Error>
) -> Result<Vec<QueryProject>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::TryStreamExt;
let mod_ids_parsed: Vec<i64> = mod_ids.into_iter().map(|x| x.0).collect();
let project_ids_parsed: Vec<i64> = project_ids.into_iter().map(|x| x.0).collect();
sqlx::query!(
"
SELECT m.id id, m.title title, m.description description, m.downloads downloads, m.follows follows,
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
m.updated updated, m.status status,
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug,
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name,
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions
FROM mods m
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id
LEFT OUTER JOIN versions v ON v.mod_id = m.id
INNER JOIN project_types pt ON pt.id = m.project_type
INNER JOIN statuses s ON s.id = m.status
INNER JOIN side_types cs ON m.client_side = cs.id
INNER JOIN side_types ss ON m.server_side = ss.id
INNER JOIN licenses l ON m.license = l.id
WHERE m.id IN (SELECT * FROM UNNEST($1::bigint[]))
GROUP BY m.id, s.id, cs.id, ss.id, l.id;
GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;
",
&mod_ids_parsed
&project_ids_parsed
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|m| QueryMod {
inner: Mod {
id: ModId(m.id),
Ok(e.right().map(|m| QueryProject {
inner: Project {
id: ProjectId(m.id),
project_type: ProjectTypeId(m.project_type),
team_id: TeamId(m.team_id),
title: m.title.clone(),
description: m.description.clone(),
@@ -567,30 +691,31 @@ impl Mod {
body: m.body.clone(),
follows: m.follows
},
project_type: m.project_type_name,
categories: m.categories.unwrap_or_default().split(',').map(|x| x.to_string()).collect(),
versions: m.versions.unwrap_or_default().split(',').map(|x| VersionId(x.parse().unwrap_or_default())).collect(),
donation_urls: vec![],
status: crate::models::mods::ModStatus::from_str(&m.status_name),
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
license_id: m.short,
license_name: m.license_name,
client_side: crate::models::mods::SideType::from_str(&m.client_side_type),
server_side: crate::models::mods::SideType::from_str(&m.server_side_type),
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
}))
})
.try_collect::<Vec<QueryMod>>()
.try_collect::<Vec<QueryProject>>()
.await
}
}
pub struct QueryMod {
pub inner: Mod,
#[derive(Clone, Debug)]
pub struct QueryProject {
pub inner: Project,
pub project_type: String,
pub categories: Vec<String>,
pub versions: Vec<VersionId>,
pub donation_urls: Vec<DonationUrl>,
pub status: crate::models::mods::ModStatus,
pub status: crate::models::projects::ProjectStatus,
pub license_id: String,
pub license_name: String,
pub client_side: crate::models::mods::SideType,
pub server_side: crate::models::mods::SideType,
pub client_side: crate::models::projects::SideType,
pub server_side: crate::models::projects::SideType,
}

View File

@@ -3,7 +3,7 @@ use super::ids::*;
pub struct Report {
pub id: ReportId,
pub report_type_id: ReportTypeId,
pub mod_id: Option<ModId>,
pub project_id: Option<ProjectId>,
pub version_id: Option<VersionId>,
pub user_id: Option<UserId>,
pub body: String,
@@ -14,7 +14,7 @@ pub struct Report {
pub struct QueryReport {
pub id: ReportId,
pub report_type: String,
pub mod_id: Option<ModId>,
pub project_id: Option<ProjectId>,
pub version_id: Option<VersionId>,
pub user_id: Option<UserId>,
pub body: String,
@@ -40,7 +40,7 @@ impl Report {
",
self.id as ReportId,
self.report_type_id as ReportTypeId,
self.mod_id.map(|x| x.0 as i64),
self.project_id.map(|x| x.0 as i64),
self.version_id.map(|x| x.0 as i64),
self.user_id.map(|x| x.0 as i64),
self.body,
@@ -72,7 +72,7 @@ impl Report {
Ok(Some(QueryReport {
id,
report_type: row.name,
mod_id: row.mod_id.map(ModId),
project_id: row.mod_id.map(ProjectId),
version_id: row.version_id.map(VersionId),
user_id: row.user_id.map(UserId),
body: row.body,
@@ -108,7 +108,7 @@ impl Report {
Ok(e.right().map(|row| QueryReport {
id: ReportId(row.id),
report_type: row.name,
mod_id: row.mod_id.map(ModId),
project_id: row.mod_id.map(ProjectId),
version_id: row.version_id.map(VersionId),
user_id: row.user_id.map(UserId),
body: row.body,

View File

@@ -61,7 +61,7 @@ impl TeamBuilder {
}
}
/// A team of users who control a mod
/// A team of users who control a project
pub struct Team {
/// The id of the team
pub id: TeamId,
@@ -412,8 +412,8 @@ impl TeamMember {
Ok(())
}
pub async fn get_from_user_id_mod<'a, 'b, E>(
id: ModId,
pub async fn get_from_user_id_project<'a, 'b, E>(
id: ProjectId,
user_id: UserId,
executor: E,
) -> Result<Option<Self>, super::DatabaseError>
@@ -426,7 +426,7 @@ impl TeamMember {
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 AND accepted = TRUE
WHERE m.id = $1
",
id as ModId,
id as ProjectId,
user_id as UserId
)
.fetch_optional(executor)

View File

@@ -1,4 +1,4 @@
use super::ids::{ModId, UserId};
use super::ids::{ProjectId, UserId};
pub struct User {
pub id: UserId,
@@ -24,7 +24,7 @@ impl User {
avatar_url, bio, created
)
VALUES (
$1, $2, LOWER($3), $4, $5,
$1, $2, $3, $4, $5,
$6, $7, $8
)
",
@@ -186,17 +186,17 @@ impl User {
Ok(users)
}
pub async fn get_mods<'a, E>(
pub async fn get_projects<'a, E>(
user_id: UserId,
status: &str,
exec: E,
) -> Result<Vec<ModId>, sqlx::Error>
) -> Result<Vec<ProjectId>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;
let mods = sqlx::query!(
let projects = sqlx::query!(
"
SELECT m.id FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id
@@ -206,23 +206,23 @@ impl User {
status,
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|m| ModId(m.id))) })
.try_collect::<Vec<ModId>>()
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) })
.try_collect::<Vec<ProjectId>>()
.await?;
Ok(mods)
Ok(projects)
}
pub async fn get_mods_private<'a, E>(
pub async fn get_projects_private<'a, E>(
user_id: UserId,
exec: E,
) -> Result<Vec<ModId>, sqlx::Error>
) -> Result<Vec<ProjectId>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;
let mods = sqlx::query!(
let projects = sqlx::query!(
"
SELECT m.id FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id
@@ -231,11 +231,11 @@ impl User {
user_id as UserId,
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|m| ModId(m.id))) })
.try_collect::<Vec<ModId>>()
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) })
.try_collect::<Vec<ProjectId>>()
.await?;
Ok(mods)
Ok(projects)
}
pub async fn remove<'a, 'b, E>(id: UserId, exec: E) -> Result<Option<()>, sqlx::error::Error>
@@ -353,7 +353,7 @@ impl User {
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::TryStreamExt;
let mods: Vec<ModId> = sqlx::query!(
let projects: Vec<ProjectId> = sqlx::query!(
"
SELECT m.id FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id
@@ -363,12 +363,12 @@ impl User {
crate::models::teams::OWNER_ROLE
)
.fetch_many(exec)
.try_filter_map(|e| async { Ok(e.right().map(|m| ModId(m.id))) })
.try_collect::<Vec<ModId>>()
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) })
.try_collect::<Vec<ProjectId>>()
.await?;
for mod_id in mods {
let _result = super::mod_item::Mod::remove_full(mod_id, exec).await?;
for project_id in projects {
let _result = super::project_item::Project::remove_full(project_id, exec).await?;
}
let notifications: Vec<i64> = sqlx::query!(
@@ -439,4 +439,56 @@ impl User {
Ok(Some(()))
}
pub async fn get_id_from_username_or_id<'a, 'b, E>(
username_or_id: String,
executor: E,
) -> Result<Option<UserId>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let id_option = crate::models::ids::base62_impl::parse_base62(&*username_or_id).ok();
if let Some(id) = id_option {
let id = UserId(id as i64);
let mut user_id = sqlx::query!(
"
SELECT id FROM users
WHERE id = $1
",
id as UserId
)
.fetch_optional(executor)
.await?
.map(|x| UserId(x.id));
if user_id.is_none() {
user_id = sqlx::query!(
"
SELECT id FROM users
WHERE LOWER(username) = LOWER($1)
",
username_or_id
)
.fetch_optional(executor)
.await?
.map(|x| UserId(x.id));
}
Ok(user_id)
} else {
let id = sqlx::query!(
"
SELECT id FROM users
WHERE LOWER(username) = LOWER($1)
",
username_or_id
)
.fetch_optional(executor)
.await?;
Ok(id.map(|x| UserId(x.id)))
}
}
}

View File

@@ -4,7 +4,7 @@ use std::collections::HashMap;
pub struct VersionBuilder {
pub version_id: VersionId,
pub mod_id: ModId,
pub project_id: ProjectId,
pub author_id: UserId,
pub name: String,
pub version_number: String,
@@ -75,7 +75,7 @@ impl VersionBuilder {
) -> Result<VersionId, DatabaseError> {
let version = Version {
id: self.version_id,
mod_id: self.mod_id,
project_id: self.project_id,
author_id: self.author_id,
name: self.name,
version_number: self.version_number,
@@ -95,7 +95,7 @@ impl VersionBuilder {
SET updated = NOW()
WHERE id = $1
",
self.mod_id as ModId,
self.project_id as ProjectId,
)
.execute(&mut *transaction)
.await?;
@@ -150,7 +150,7 @@ impl VersionBuilder {
pub struct Version {
pub id: VersionId,
pub mod_id: ModId,
pub project_id: ProjectId,
pub author_id: UserId,
pub name: String,
pub version_number: String,
@@ -182,7 +182,7 @@ impl Version {
)
",
self.id as VersionId,
self.mod_id as ModId,
self.project_id as ProjectId,
self.author_id as UserId,
&self.name,
&self.version_number,
@@ -359,8 +359,8 @@ impl Version {
Ok(vec)
}
pub async fn get_mod_versions<'a, E>(
mod_id: ModId,
pub async fn get_project_versions<'a, E>(
project_id: ProjectId,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
exec: E,
@@ -382,7 +382,7 @@ impl Version {
) AS version
ORDER BY version.date_published ASC
",
mod_id as ModId,
project_id as ProjectId,
&game_versions.unwrap_or_default(),
&loaders.unwrap_or_default(),
)
@@ -417,7 +417,7 @@ impl Version {
if let Some(row) = result {
Ok(Some(Version {
id,
mod_id: ModId(row.mod_id),
project_id: ProjectId(row.mod_id),
author_id: UserId(row.author_id),
name: row.name,
version_number: row.version_number,
@@ -450,6 +450,7 @@ impl Version {
v.release_channel, v.featured
FROM versions v
WHERE v.id IN (SELECT * FROM UNNEST($1::bigint[]))
ORDER BY v.date_published ASC
",
&version_ids_parsed
)
@@ -457,7 +458,7 @@ impl Version {
.try_filter_map(|e| async {
Ok(e.right().map(|v| Version {
id: VersionId(v.id),
mod_id: ModId(v.mod_id),
project_id: ProjectId(v.mod_id),
author_id: UserId(v.author_id),
name: v.name,
version_number: v.version_number,
@@ -480,7 +481,7 @@ impl Version {
executor: E,
) -> Result<Option<QueryVersion>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
@@ -566,7 +567,7 @@ impl Version {
Ok(Some(QueryVersion {
id: VersionId(v.id),
mod_id: ModId(v.mod_id),
project_id: ProjectId(v.mod_id),
author_id: UserId(v.author_id),
name: v.version_name,
version_number: v.version_number,
@@ -625,7 +626,8 @@ impl Version {
LEFT OUTER JOIN hashes h on f.id = h.file_id
LEFT OUTER JOIN dependencies d on v.id = d.dependent_id
WHERE v.id IN (SELECT * FROM UNNEST($1::bigint[]))
GROUP BY v.id, rc.id;
GROUP BY v.id, rc.id
ORDER BY v.date_published ASC;
",
&version_ids_parsed
)
@@ -683,7 +685,7 @@ impl Version {
QueryVersion {
id: VersionId(v.id),
mod_id: ModId(v.mod_id),
project_id: ProjectId(v.mod_id),
author_id: UserId(v.author_id),
name: v.version_name,
version_number: v.version_number,
@@ -727,7 +729,7 @@ pub struct FileHash {
#[derive(Clone)]
pub struct QueryVersion {
pub id: VersionId,
pub mod_id: ModId,
pub project_id: ProjectId,
pub author_id: UserId,
pub name: String,
pub version_number: String,