You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
",
|
||||
¬ification_ids_parsed
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user