Initial work on new status sys + scheduling releases (#489)
* Initial work on new status sys + scheduling releases * Finish project statuses + begin work on version statuses * Finish version statuses * Regenerate prepare * Run fmt + clippy
This commit is contained in:
parent
c34e2ab3e1
commit
e96d23cc3f
23
migrations/20221129161609_status-types-changes.sql
Normal file
23
migrations/20221129161609_status-types-changes.sql
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
ALTER TABLE mods ADD COLUMN updated_status varchar(128) NULL;
|
||||||
|
ALTER TABLE mods ADD COLUMN requested_status varchar(128) NULL;
|
||||||
|
|
||||||
|
UPDATE mods
|
||||||
|
SET updated_status = (
|
||||||
|
SELECT s.status
|
||||||
|
FROM statuses s
|
||||||
|
WHERE s.id = mods.status
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE mods
|
||||||
|
DROP COLUMN status;
|
||||||
|
|
||||||
|
ALTER TABLE mods
|
||||||
|
RENAME COLUMN updated_status TO status;
|
||||||
|
|
||||||
|
DROP TABLE statuses;
|
||||||
|
|
||||||
|
ALTER TABLE mods ALTER COLUMN status SET NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABlE versions ADD COLUMN status varchar(128) NOT NULL DEFAULT 'listed';
|
||||||
|
ALTER TABLE versions ADD COLUMN requested_status varchar(128) NULL;
|
||||||
3092
sqlx-data.json
3092
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@ -18,9 +18,7 @@ pub use project_item::Project;
|
|||||||
pub use team_item::Team;
|
pub use team_item::Team;
|
||||||
pub use team_item::TeamMember;
|
pub use team_item::TeamMember;
|
||||||
pub use user_item::User;
|
pub use user_item::User;
|
||||||
pub use version_item::FileHash;
|
|
||||||
pub use version_item::Version;
|
pub use version_item::Version;
|
||||||
pub use version_item::VersionFile;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum DatabaseError {
|
pub enum DatabaseError {
|
||||||
@ -32,28 +30,6 @@ pub enum DatabaseError {
|
|||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ids::StatusId {
|
|
||||||
pub async fn get_id<'a, E>(
|
|
||||||
status: &crate::models::projects::ProjectStatus,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<Self>, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT id FROM statuses
|
|
||||||
WHERE status = $1
|
|
||||||
",
|
|
||||||
status.as_str()
|
|
||||||
)
|
|
||||||
.fetch_optional(exec)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(result.map(|r| ids::StatusId(r.id)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ids::SideTypeId {
|
impl ids::SideTypeId {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(
|
||||||
side: &crate::models::projects::SideType,
|
side: &crate::models::projects::SideType,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::ids::*;
|
use super::ids::*;
|
||||||
use crate::database::models::convert_postgres_date;
|
use crate::database::models::convert_postgres_date;
|
||||||
|
use crate::models::projects::ProjectStatus;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -89,7 +90,8 @@ pub struct ProjectBuilder {
|
|||||||
pub categories: Vec<CategoryId>,
|
pub categories: Vec<CategoryId>,
|
||||||
pub additional_categories: Vec<CategoryId>,
|
pub additional_categories: Vec<CategoryId>,
|
||||||
pub initial_versions: Vec<super::version_item::VersionBuilder>,
|
pub initial_versions: Vec<super::version_item::VersionBuilder>,
|
||||||
pub status: StatusId,
|
pub status: ProjectStatus,
|
||||||
|
pub requested_status: Option<ProjectStatus>,
|
||||||
pub client_side: SideTypeId,
|
pub client_side: SideTypeId,
|
||||||
pub server_side: SideTypeId,
|
pub server_side: SideTypeId,
|
||||||
pub license: String,
|
pub license: String,
|
||||||
@ -115,6 +117,7 @@ impl ProjectBuilder {
|
|||||||
updated: Utc::now(),
|
updated: Utc::now(),
|
||||||
approved: None,
|
approved: None,
|
||||||
status: self.status,
|
status: self.status,
|
||||||
|
requested_status: self.requested_status,
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
follows: 0,
|
follows: 0,
|
||||||
icon_url: self.icon_url,
|
icon_url: self.icon_url,
|
||||||
@ -190,7 +193,8 @@ pub struct Project {
|
|||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
pub updated: DateTime<Utc>,
|
pub updated: DateTime<Utc>,
|
||||||
pub approved: Option<DateTime<Utc>>,
|
pub approved: Option<DateTime<Utc>>,
|
||||||
pub status: StatusId,
|
pub status: ProjectStatus,
|
||||||
|
pub requested_status: Option<ProjectStatus>,
|
||||||
pub downloads: i32,
|
pub downloads: i32,
|
||||||
pub follows: i32,
|
pub follows: i32,
|
||||||
pub icon_url: Option<String>,
|
pub icon_url: Option<String>,
|
||||||
@ -219,16 +223,16 @@ impl Project {
|
|||||||
INSERT INTO mods (
|
INSERT INTO mods (
|
||||||
id, team_id, title, description, body,
|
id, team_id, title, description, body,
|
||||||
published, downloads, icon_url, issues_url,
|
published, downloads, icon_url, issues_url,
|
||||||
source_url, wiki_url, status, discord_url,
|
source_url, wiki_url, status, requested_status, discord_url,
|
||||||
client_side, server_side, license_url, license,
|
client_side, server_side, license_url, license,
|
||||||
slug, project_type
|
slug, project_type
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7, $8, $9,
|
$6, $7, $8, $9,
|
||||||
$10, $11, $12, $13,
|
$10, $11, $12, $13, $14,
|
||||||
$14, $15, $16, $17,
|
$15, $16, $17, $18,
|
||||||
LOWER($18), $19
|
LOWER($19), $20
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as ProjectId,
|
self.id as ProjectId,
|
||||||
@ -242,7 +246,8 @@ impl Project {
|
|||||||
self.issues_url.as_ref(),
|
self.issues_url.as_ref(),
|
||||||
self.source_url.as_ref(),
|
self.source_url.as_ref(),
|
||||||
self.wiki_url.as_ref(),
|
self.wiki_url.as_ref(),
|
||||||
self.status.0,
|
self.status.as_str(),
|
||||||
|
self.requested_status.map(|x| x.as_str()),
|
||||||
self.discord_url.as_ref(),
|
self.discord_url.as_ref(),
|
||||||
self.client_side as SideTypeId,
|
self.client_side as SideTypeId,
|
||||||
self.server_side as SideTypeId,
|
self.server_side as SideTypeId,
|
||||||
@ -268,7 +273,7 @@ impl Project {
|
|||||||
"
|
"
|
||||||
SELECT project_type, title, description, downloads, follows,
|
SELECT project_type, title, description, downloads, follows,
|
||||||
icon_url, body, body_url, published,
|
icon_url, body, body_url, published,
|
||||||
updated, approved, status,
|
updated, approved, status, requested_status,
|
||||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||||
team_id, client_side, server_side, license, slug,
|
team_id, client_side, server_side, license, slug,
|
||||||
moderation_message, moderation_message_body, flame_anvil_project,
|
moderation_message, moderation_message_body, flame_anvil_project,
|
||||||
@ -299,7 +304,10 @@ impl Project {
|
|||||||
license_url: row.license_url,
|
license_url: row.license_url,
|
||||||
discord_url: row.discord_url,
|
discord_url: row.discord_url,
|
||||||
client_side: SideTypeId(row.client_side),
|
client_side: SideTypeId(row.client_side),
|
||||||
status: StatusId(row.status),
|
status: ProjectStatus::from_str(&row.status),
|
||||||
|
requested_status: row
|
||||||
|
.requested_status
|
||||||
|
.map(|x| ProjectStatus::from_str(&x)),
|
||||||
server_side: SideTypeId(row.server_side),
|
server_side: SideTypeId(row.server_side),
|
||||||
license: row.license,
|
license: row.license,
|
||||||
slug: row.slug,
|
slug: row.slug,
|
||||||
@ -331,7 +339,7 @@ impl Project {
|
|||||||
"
|
"
|
||||||
SELECT id, project_type, title, description, downloads, follows,
|
SELECT id, project_type, title, description, downloads, follows,
|
||||||
icon_url, body, body_url, published,
|
icon_url, body, body_url, published,
|
||||||
updated, approved, status,
|
updated, approved, status, requested_status,
|
||||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||||
team_id, client_side, server_side, license, slug,
|
team_id, client_side, server_side, license, slug,
|
||||||
moderation_message, moderation_message_body, flame_anvil_project,
|
moderation_message, moderation_message_body, flame_anvil_project,
|
||||||
@ -360,7 +368,12 @@ impl Project {
|
|||||||
license_url: m.license_url,
|
license_url: m.license_url,
|
||||||
discord_url: m.discord_url,
|
discord_url: m.discord_url,
|
||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: ProjectStatus::from_str(
|
||||||
|
&m.status,
|
||||||
|
),
|
||||||
|
requested_status: m.requested_status.map(|x| ProjectStatus::from_str(
|
||||||
|
&x,
|
||||||
|
)),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: m.license,
|
license: m.license,
|
||||||
slug: m.slug,
|
slug: m.slug,
|
||||||
@ -370,7 +383,7 @@ impl Project {
|
|||||||
moderation_message_body: m.moderation_message_body,
|
moderation_message_body: m.moderation_message_body,
|
||||||
approved: m.approved,
|
approved: m.approved,
|
||||||
flame_anvil_project: m.flame_anvil_project,
|
flame_anvil_project: m.flame_anvil_project,
|
||||||
flame_anvil_user: m.flame_anvil_user.map(UserId)
|
flame_anvil_user: m.flame_anvil_user.map(UserId),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<Project>>()
|
.try_collect::<Vec<Project>>()
|
||||||
@ -646,29 +659,29 @@ impl Project {
|
|||||||
"
|
"
|
||||||
SELECT m.id id, m.project_type project_type, 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.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
|
||||||
m.updated updated, m.approved approved, m.status status,
|
m.updated updated, m.approved approved, m.status status, m.requested_status requested_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.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, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||||
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
||||||
FROM mods m
|
FROM mods m
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
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 cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||||
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||||
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
||||||
LEFT JOIN versions v ON v.mod_id = m.id
|
LEFT JOIN versions v ON v.mod_id = m.id AND v.status = ANY($2)
|
||||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
WHERE m.id = $1
|
WHERE m.id = $1
|
||||||
GROUP BY pt.id, s.id, cs.id, ss.id, m.id;
|
GROUP BY pt.id, cs.id, ss.id, m.id;
|
||||||
",
|
",
|
||||||
id as ProjectId,
|
id as ProjectId,
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_listed()).map(|x| x.to_string()).collect::<Vec<String>>()
|
||||||
)
|
)
|
||||||
.fetch_optional(executor)
|
.fetch_optional(executor)
|
||||||
.await?;
|
.await?;
|
||||||
@ -709,7 +722,10 @@ impl Project {
|
|||||||
license_url: m.license_url.clone(),
|
license_url: m.license_url.clone(),
|
||||||
discord_url: m.discord_url.clone(),
|
discord_url: m.discord_url.clone(),
|
||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: ProjectStatus::from_str(&m.status),
|
||||||
|
requested_status: m
|
||||||
|
.requested_status
|
||||||
|
.map(|x| ProjectStatus::from_str(&x)),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: m.license.clone(),
|
license: m.license.clone(),
|
||||||
slug: m.slug.clone(),
|
slug: m.slug.clone(),
|
||||||
@ -802,9 +818,6 @@ impl Project {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
status: crate::models::projects::ProjectStatus::from_str(
|
|
||||||
&m.status_name,
|
|
||||||
),
|
|
||||||
client_side: crate::models::projects::SideType::from_str(
|
client_side: crate::models::projects::SideType::from_str(
|
||||||
&m.client_side_type,
|
&m.client_side_type,
|
||||||
),
|
),
|
||||||
@ -832,29 +845,29 @@ impl Project {
|
|||||||
"
|
"
|
||||||
SELECT m.id id, m.project_type project_type, 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.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
|
||||||
m.updated updated, m.approved approved, m.status status,
|
m.updated updated, m.approved approved, m.status status, m.requested_status requested_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.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, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||||
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
||||||
FROM mods m
|
FROM mods m
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
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 cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||||
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||||
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
||||||
LEFT JOIN versions v ON v.mod_id = m.id
|
LEFT JOIN versions v ON v.mod_id = m.id AND v.status = ANY($2)
|
||||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
WHERE m.id = ANY($1)
|
WHERE m.id = ANY($1)
|
||||||
GROUP BY pt.id, s.id, cs.id, ss.id, m.id;
|
GROUP BY pt.id, cs.id, ss.id, m.id;
|
||||||
",
|
",
|
||||||
&project_ids_parsed
|
&project_ids_parsed,
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_listed()).map(|x| x.to_string()).collect::<Vec<String>>()
|
||||||
)
|
)
|
||||||
.fetch_many(exec)
|
.fetch_many(exec)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
@ -897,7 +910,12 @@ impl Project {
|
|||||||
license_url: m.license_url.clone(),
|
license_url: m.license_url.clone(),
|
||||||
discord_url: m.discord_url.clone(),
|
discord_url: m.discord_url.clone(),
|
||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: ProjectStatus::from_str(
|
||||||
|
&m.status,
|
||||||
|
),
|
||||||
|
requested_status: m.requested_status.map(|x| ProjectStatus::from_str(
|
||||||
|
&x,
|
||||||
|
)),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: m.license.clone(),
|
license: m.license.clone(),
|
||||||
slug: m.slug.clone(),
|
slug: m.slug.clone(),
|
||||||
@ -978,7 +996,6 @@ impl Project {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
|
|
||||||
client_side: crate::models::projects::SideType::from_str(&m.client_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),
|
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
|
||||||
}}))
|
}}))
|
||||||
@ -997,7 +1014,6 @@ pub struct QueryProject {
|
|||||||
pub versions: Vec<VersionId>,
|
pub versions: Vec<VersionId>,
|
||||||
pub donation_urls: Vec<DonationUrl>,
|
pub donation_urls: Vec<DonationUrl>,
|
||||||
pub gallery_items: Vec<GalleryItem>,
|
pub gallery_items: Vec<GalleryItem>,
|
||||||
pub status: crate::models::projects::ProjectStatus,
|
|
||||||
pub client_side: crate::models::projects::SideType,
|
pub client_side: crate::models::projects::SideType,
|
||||||
pub server_side: crate::models::projects::SideType,
|
pub server_side: crate::models::projects::SideType,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,34 +255,6 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_projects<'a, E>(
|
pub async fn get_projects<'a, E>(
|
||||||
user_id: UserId,
|
|
||||||
status: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Vec<ProjectId>, sqlx::Error>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
||||||
{
|
|
||||||
use futures::stream::TryStreamExt;
|
|
||||||
|
|
||||||
let projects = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT m.id FROM mods m
|
|
||||||
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE
|
|
||||||
WHERE tm.user_id = $1 AND m.status = (SELECT s.id FROM statuses s WHERE s.status = $2)
|
|
||||||
ORDER BY m.downloads DESC
|
|
||||||
",
|
|
||||||
user_id as UserId,
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
.fetch_many(exec)
|
|
||||||
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) })
|
|
||||||
.try_collect::<Vec<ProjectId>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(projects)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_projects_private<'a, E>(
|
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
exec: E,
|
exec: E,
|
||||||
) -> Result<Vec<ProjectId>, sqlx::Error>
|
) -> Result<Vec<ProjectId>, sqlx::Error>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use super::ids::*;
|
use super::ids::*;
|
||||||
use super::DatabaseError;
|
use super::DatabaseError;
|
||||||
use crate::database::models::convert_postgres_date;
|
use crate::database::models::convert_postgres_date;
|
||||||
|
use crate::models::projects::VersionStatus;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -18,6 +19,8 @@ pub struct VersionBuilder {
|
|||||||
pub loaders: Vec<LoaderId>,
|
pub loaders: Vec<LoaderId>,
|
||||||
pub version_type: String,
|
pub version_type: String,
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
pub status: VersionStatus,
|
||||||
|
pub requested_status: Option<VersionStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DependencyBuilder {
|
pub struct DependencyBuilder {
|
||||||
@ -149,6 +152,8 @@ impl VersionBuilder {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
featured: self.featured,
|
featured: self.featured,
|
||||||
version_type: self.version_type,
|
version_type: self.version_type,
|
||||||
|
status: self.status,
|
||||||
|
requested_status: self.requested_status,
|
||||||
};
|
};
|
||||||
|
|
||||||
version.insert(&mut *transaction).await?;
|
version.insert(&mut *transaction).await?;
|
||||||
@ -237,6 +242,7 @@ impl VersionBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
pub id: VersionId,
|
pub id: VersionId,
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
@ -249,6 +255,8 @@ pub struct Version {
|
|||||||
pub downloads: i32,
|
pub downloads: i32,
|
||||||
pub version_type: String,
|
pub version_type: String,
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
pub status: VersionStatus,
|
||||||
|
pub requested_status: Option<VersionStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
@ -261,13 +269,13 @@ impl Version {
|
|||||||
INSERT INTO versions (
|
INSERT INTO versions (
|
||||||
id, mod_id, author_id, name, version_number,
|
id, mod_id, author_id, name, version_number,
|
||||||
changelog, changelog_url, date_published,
|
changelog, changelog_url, date_published,
|
||||||
downloads, version_type, featured
|
downloads, version_type, featured, status
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5,
|
||||||
$6, $7,
|
$6, $7,
|
||||||
$8, $9,
|
$8, $9,
|
||||||
$10, $11
|
$10, $11, $12
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as VersionId,
|
self.id as VersionId,
|
||||||
@ -280,7 +288,8 @@ impl Version {
|
|||||||
self.date_published,
|
self.date_published,
|
||||||
self.downloads,
|
self.downloads,
|
||||||
&self.version_type,
|
&self.version_type,
|
||||||
self.featured
|
self.featured,
|
||||||
|
self.status.as_str()
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -361,37 +370,6 @@ impl Version {
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let files = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT files.id, files.url, files.filename, files.is_primary FROM files
|
|
||||||
WHERE files.version_id = $1
|
|
||||||
",
|
|
||||||
id as VersionId,
|
|
||||||
)
|
|
||||||
.fetch_many(&mut *transaction)
|
|
||||||
.try_filter_map(|e| async {
|
|
||||||
Ok(e.right().map(|c| VersionFile {
|
|
||||||
id: FileId(c.id),
|
|
||||||
version_id: id,
|
|
||||||
url: c.url,
|
|
||||||
filename: c.filename,
|
|
||||||
primary: c.is_primary,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.try_collect::<Vec<VersionFile>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for file in files {
|
|
||||||
// TODO: store backblaze id in database so that we can delete the files here
|
|
||||||
// For now, we can't delete the files since we don't have the backblaze id
|
|
||||||
log::warn!(
|
|
||||||
"Can't delete version file id: {} (url: {}, name: {})",
|
|
||||||
file.id.0,
|
|
||||||
file.url,
|
|
||||||
file.filename
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
DELETE FROM hashes
|
DELETE FROM hashes
|
||||||
@ -531,7 +509,7 @@ impl Version {
|
|||||||
"
|
"
|
||||||
SELECT v.mod_id, v.author_id, v.name, v.version_number,
|
SELECT v.mod_id, v.author_id, v.name, v.version_number,
|
||||||
v.changelog, v.changelog_url, v.date_published, v.downloads,
|
v.changelog, v.changelog_url, v.date_published, v.downloads,
|
||||||
v.version_type, v.featured
|
v.version_type, v.featured, v.status, v.requested_status
|
||||||
FROM versions v
|
FROM versions v
|
||||||
WHERE v.id = $1
|
WHERE v.id = $1
|
||||||
",
|
",
|
||||||
@ -553,6 +531,10 @@ impl Version {
|
|||||||
downloads: row.downloads,
|
downloads: row.downloads,
|
||||||
version_type: row.version_type,
|
version_type: row.version_type,
|
||||||
featured: row.featured,
|
featured: row.featured,
|
||||||
|
status: VersionStatus::from_str(&row.status),
|
||||||
|
requested_status: row
|
||||||
|
.requested_status
|
||||||
|
.map(|x| VersionStatus::from_str(&x)),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -574,7 +556,7 @@ impl Version {
|
|||||||
"
|
"
|
||||||
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
|
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
|
||||||
v.changelog, v.changelog_url, v.date_published, v.downloads,
|
v.changelog, v.changelog_url, v.date_published, v.downloads,
|
||||||
v.version_type, v.featured
|
v.version_type, v.featured, v.status, v.requested_status
|
||||||
FROM versions v
|
FROM versions v
|
||||||
WHERE v.id = ANY($1)
|
WHERE v.id = ANY($1)
|
||||||
ORDER BY v.date_published ASC
|
ORDER BY v.date_published ASC
|
||||||
@ -595,6 +577,10 @@ impl Version {
|
|||||||
downloads: v.downloads,
|
downloads: v.downloads,
|
||||||
featured: v.featured,
|
featured: v.featured,
|
||||||
version_type: v.version_type,
|
version_type: v.version_type,
|
||||||
|
status: VersionStatus::from_str(&v.status),
|
||||||
|
requested_status: v
|
||||||
|
.requested_status
|
||||||
|
.map(|x| VersionStatus::from_str(&x)),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<Version>>()
|
.try_collect::<Vec<Version>>()
|
||||||
@ -614,7 +600,7 @@ impl Version {
|
|||||||
"
|
"
|
||||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||||
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
||||||
v.version_type version_type, v.featured featured,
|
v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,
|
||||||
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||||
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
||||||
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
||||||
@ -637,6 +623,7 @@ impl Version {
|
|||||||
|
|
||||||
if let Some(v) = result {
|
if let Some(v) = result {
|
||||||
Ok(Some(QueryVersion {
|
Ok(Some(QueryVersion {
|
||||||
|
inner: Version {
|
||||||
id: VersionId(v.id),
|
id: VersionId(v.id),
|
||||||
project_id: ProjectId(v.mod_id),
|
project_id: ProjectId(v.mod_id),
|
||||||
author_id: UserId(v.author_id),
|
author_id: UserId(v.author_id),
|
||||||
@ -646,6 +633,13 @@ impl Version {
|
|||||||
changelog_url: v.changelog_url,
|
changelog_url: v.changelog_url,
|
||||||
date_published: v.date_published,
|
date_published: v.date_published,
|
||||||
downloads: v.downloads,
|
downloads: v.downloads,
|
||||||
|
version_type: v.version_type,
|
||||||
|
featured: v.featured,
|
||||||
|
status: VersionStatus::from_str(&v.status),
|
||||||
|
requested_status: v
|
||||||
|
.requested_status
|
||||||
|
.map(|x| VersionStatus::from_str(&x)),
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
let hashes: Vec<(FileId, String, Vec<u8>)> = v
|
let hashes: Vec<(FileId, String, Vec<u8>)> = v
|
||||||
.hashes
|
.hashes
|
||||||
@ -737,7 +731,6 @@ impl Version {
|
|||||||
gv.into_iter().map(|x| x.0).collect()
|
gv.into_iter().map(|x| x.0).collect()
|
||||||
},
|
},
|
||||||
loaders: v.loaders.unwrap_or_default(),
|
loaders: v.loaders.unwrap_or_default(),
|
||||||
featured: v.featured,
|
|
||||||
dependencies: v
|
dependencies: v
|
||||||
.dependencies
|
.dependencies
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@ -773,7 +766,6 @@ impl Version {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
version_type: v.version_type,
|
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -795,7 +787,7 @@ impl Version {
|
|||||||
"
|
"
|
||||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||||
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
||||||
v.version_type version_type, v.featured featured,
|
v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,
|
||||||
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||||
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
||||||
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
||||||
@ -818,6 +810,7 @@ impl Version {
|
|||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|v|
|
Ok(e.right().map(|v|
|
||||||
QueryVersion {
|
QueryVersion {
|
||||||
|
inner: Version {
|
||||||
id: VersionId(v.id),
|
id: VersionId(v.id),
|
||||||
project_id: ProjectId(v.mod_id),
|
project_id: ProjectId(v.mod_id),
|
||||||
author_id: UserId(v.author_id),
|
author_id: UserId(v.author_id),
|
||||||
@ -827,6 +820,12 @@ impl Version {
|
|||||||
changelog_url: v.changelog_url,
|
changelog_url: v.changelog_url,
|
||||||
date_published: v.date_published,
|
date_published: v.date_published,
|
||||||
downloads: v.downloads,
|
downloads: v.downloads,
|
||||||
|
version_type: v.version_type,
|
||||||
|
featured: v.featured,
|
||||||
|
status: VersionStatus::from_str(&v.status),
|
||||||
|
requested_status: v.requested_status
|
||||||
|
.map(|x| VersionStatus::from_str(&x)),
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
let hashes: Vec<(FileId, String, Vec<u8>)> = v.hashes.unwrap_or_default()
|
let hashes: Vec<(FileId, String, Vec<u8>)> = v.hashes.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -909,7 +908,6 @@ impl Version {
|
|||||||
.collect()
|
.collect()
|
||||||
},
|
},
|
||||||
loaders: v.loaders.unwrap_or_default(),
|
loaders: v.loaders.unwrap_or_default(),
|
||||||
featured: v.featured,
|
|
||||||
dependencies: v.dependencies
|
dependencies: v.dependencies
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -944,7 +942,6 @@ impl Version {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
version_type: v.version_type
|
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
@ -953,37 +950,13 @@ impl Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VersionFile {
|
|
||||||
pub id: FileId,
|
|
||||||
pub version_id: VersionId,
|
|
||||||
pub url: String,
|
|
||||||
pub filename: String,
|
|
||||||
pub primary: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileHash {
|
|
||||||
pub file_id: FileId,
|
|
||||||
pub algorithm: String,
|
|
||||||
pub hash: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QueryVersion {
|
pub struct QueryVersion {
|
||||||
pub id: VersionId,
|
pub inner: Version,
|
||||||
pub project_id: ProjectId,
|
|
||||||
pub author_id: UserId,
|
|
||||||
pub name: String,
|
|
||||||
pub version_number: String,
|
|
||||||
pub changelog: String,
|
|
||||||
pub changelog_url: Option<String>,
|
|
||||||
pub date_published: DateTime<Utc>,
|
|
||||||
pub downloads: i32,
|
|
||||||
|
|
||||||
pub version_type: String,
|
|
||||||
pub files: Vec<QueryFile>,
|
pub files: Vec<QueryFile>,
|
||||||
pub game_versions: Vec<String>,
|
pub game_versions: Vec<String>,
|
||||||
pub loaders: Vec<String>,
|
pub loaders: Vec<String>,
|
||||||
pub featured: bool,
|
|
||||||
pub dependencies: Vec<QueryDependency>,
|
pub dependencies: Vec<QueryDependency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
src/main.rs
51
src/main.rs
@ -121,6 +121,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Deleting old authentication states from the database every 15 minutes
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
scheduler.run(std::time::Duration::from_secs(15 * 60), move || {
|
scheduler.run(std::time::Duration::from_secs(15 * 60), move || {
|
||||||
let pool_ref = pool_ref.clone();
|
let pool_ref = pool_ref.clone();
|
||||||
@ -148,6 +149,53 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Changes statuses of scheduled projects/versions
|
||||||
|
let pool_ref = pool.clone();
|
||||||
|
scheduler.run(std::time::Duration::from_secs(60), move || {
|
||||||
|
let pool_ref = pool_ref.clone();
|
||||||
|
info!("Releasing scheduled versions/projects!");
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let projects_results = sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods
|
||||||
|
SET status = requested_status
|
||||||
|
WHERE status = $1 AND approved < CURRENT_DATE AND requested_status IS NOT NULL
|
||||||
|
",
|
||||||
|
crate::models::projects::ProjectStatus::Scheduled.as_str(),
|
||||||
|
)
|
||||||
|
.execute(&pool_ref)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(e) = projects_results {
|
||||||
|
warn!(
|
||||||
|
"Syncing scheduled releases for projects failed: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let versions_results = sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE versions
|
||||||
|
SET status = requested_status
|
||||||
|
WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL
|
||||||
|
",
|
||||||
|
crate::models::projects::VersionStatus::Scheduled.as_str(),
|
||||||
|
)
|
||||||
|
.execute(&pool_ref)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(e) = versions_results {
|
||||||
|
warn!(
|
||||||
|
"Syncing scheduled releases for versions failed: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Finished releasing scheduled versions/projects");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
scheduler::schedule_versions(&mut scheduler, pool.clone());
|
||||||
|
|
||||||
let download_queue = Arc::new(DownloadQueue::new());
|
let download_queue = Arc::new(DownloadQueue::new());
|
||||||
@ -168,13 +216,12 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new()));
|
|
||||||
|
|
||||||
let ip_salt = Pepper {
|
let ip_salt = Pepper {
|
||||||
pepper: models::ids::Base62Id(models::ids::random_base62(11))
|
pepper: models::ids::Base62Id(models::ids::random_base62(11))
|
||||||
.to_string(),
|
.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new()));
|
||||||
let flame_anvil_queue = Arc::new(Mutex::new(FlameAnvilQueue::new()));
|
let flame_anvil_queue = Arc::new(Mutex::new(FlameAnvilQueue::new()));
|
||||||
|
|
||||||
let store = MemoryStore::new();
|
let store = MemoryStore::new();
|
||||||
|
|||||||
@ -51,6 +51,9 @@ pub struct Project {
|
|||||||
|
|
||||||
/// The status of the project
|
/// The status of the project
|
||||||
pub status: ProjectStatus,
|
pub status: ProjectStatus,
|
||||||
|
/// The requested status of this projct
|
||||||
|
pub requested_status: Option<ProjectStatus>,
|
||||||
|
|
||||||
/// The rejection data of the project
|
/// The rejection data of the project
|
||||||
pub moderator_message: Option<ModeratorMessage>,
|
pub moderator_message: Option<ModeratorMessage>,
|
||||||
|
|
||||||
@ -111,7 +114,8 @@ impl From<QueryProject> for Project {
|
|||||||
published: m.published,
|
published: m.published,
|
||||||
updated: m.updated,
|
updated: m.updated,
|
||||||
approved: m.approved,
|
approved: m.approved,
|
||||||
status: data.status,
|
status: m.status,
|
||||||
|
requested_status: m.requested_status,
|
||||||
moderator_message: if let Some(message) = m.moderation_message {
|
moderator_message: if let Some(message) = m.moderation_message {
|
||||||
Some(ModeratorMessage {
|
Some(ModeratorMessage {
|
||||||
message,
|
message,
|
||||||
@ -122,7 +126,7 @@ impl From<QueryProject> for Project {
|
|||||||
},
|
},
|
||||||
license: License {
|
license: License {
|
||||||
id: m.license.clone(),
|
id: m.license.clone(),
|
||||||
name: match spdx::Expression::parse(&*m.license) {
|
name: match spdx::Expression::parse(&m.license) {
|
||||||
Ok(spdx_expr) => {
|
Ok(spdx_expr) => {
|
||||||
let mut vec: Vec<&str> = Vec::new();
|
let mut vec: Vec<&str> = Vec::new();
|
||||||
for node in spdx_expr.iter() {
|
for node in spdx_expr.iter() {
|
||||||
@ -256,8 +260,11 @@ pub struct DonationLink {
|
|||||||
/// Rejected - Project is not displayed on search, and not accessible by URL (Temporary state, project can reapply)
|
/// Rejected - Project is not displayed on search, and not accessible by URL (Temporary state, project can reapply)
|
||||||
/// Draft - Project is not displayed on search, and not accessible by URL
|
/// Draft - Project is not displayed on search, and not accessible by URL
|
||||||
/// Unlisted - Project is not displayed on search, but accessible by URL
|
/// Unlisted - Project is not displayed on search, but accessible by URL
|
||||||
|
/// Withheld - Same as unlisted, but set by a moderator. Cannot be switched to another type without moderator approval
|
||||||
/// Processing - Project is not displayed on search, and not accessible by URL (Temporary state, project under review)
|
/// Processing - Project is not displayed on search, and not accessible by URL (Temporary state, project under review)
|
||||||
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
|
/// Scheduled - Project is scheduled to be released in the future
|
||||||
|
/// Private - Project is approved, but is not viewable to the public
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ProjectStatus {
|
pub enum ProjectStatus {
|
||||||
Approved,
|
Approved,
|
||||||
@ -266,6 +273,9 @@ pub enum ProjectStatus {
|
|||||||
Draft,
|
Draft,
|
||||||
Unlisted,
|
Unlisted,
|
||||||
Processing,
|
Processing,
|
||||||
|
Withheld,
|
||||||
|
Scheduled,
|
||||||
|
Private,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +294,8 @@ impl ProjectStatus {
|
|||||||
"draft" => ProjectStatus::Draft,
|
"draft" => ProjectStatus::Draft,
|
||||||
"unlisted" => ProjectStatus::Unlisted,
|
"unlisted" => ProjectStatus::Unlisted,
|
||||||
"archived" => ProjectStatus::Archived,
|
"archived" => ProjectStatus::Archived,
|
||||||
|
"withheld" => ProjectStatus::Withheld,
|
||||||
|
"private" => ProjectStatus::Private,
|
||||||
_ => ProjectStatus::Unknown,
|
_ => ProjectStatus::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,23 +308,77 @@ impl ProjectStatus {
|
|||||||
ProjectStatus::Processing => "processing",
|
ProjectStatus::Processing => "processing",
|
||||||
ProjectStatus::Unknown => "unknown",
|
ProjectStatus::Unknown => "unknown",
|
||||||
ProjectStatus::Archived => "archived",
|
ProjectStatus::Archived => "archived",
|
||||||
|
ProjectStatus::Withheld => "withheld",
|
||||||
|
ProjectStatus::Scheduled => "scheduled",
|
||||||
|
ProjectStatus::Private => "private",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iterator() -> impl Iterator<Item = ProjectStatus> {
|
||||||
|
[
|
||||||
|
ProjectStatus::Approved,
|
||||||
|
ProjectStatus::Archived,
|
||||||
|
ProjectStatus::Rejected,
|
||||||
|
ProjectStatus::Draft,
|
||||||
|
ProjectStatus::Unlisted,
|
||||||
|
ProjectStatus::Processing,
|
||||||
|
ProjectStatus::Withheld,
|
||||||
|
ProjectStatus::Scheduled,
|
||||||
|
ProjectStatus::Private,
|
||||||
|
ProjectStatus::Unknown,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project pages + info cannot be viewed
|
||||||
pub fn is_hidden(&self) -> bool {
|
pub fn is_hidden(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ProjectStatus::Approved => false,
|
|
||||||
ProjectStatus::Rejected => true,
|
ProjectStatus::Rejected => true,
|
||||||
ProjectStatus::Draft => true,
|
ProjectStatus::Draft => true,
|
||||||
ProjectStatus::Unlisted => false,
|
|
||||||
ProjectStatus::Processing => true,
|
ProjectStatus::Processing => true,
|
||||||
ProjectStatus::Unknown => true,
|
ProjectStatus::Unknown => true,
|
||||||
|
ProjectStatus::Scheduled => true,
|
||||||
|
ProjectStatus::Private => true,
|
||||||
|
|
||||||
|
ProjectStatus::Approved => false,
|
||||||
|
ProjectStatus::Unlisted => false,
|
||||||
ProjectStatus::Archived => false,
|
ProjectStatus::Archived => false,
|
||||||
|
ProjectStatus::Withheld => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Project can be displayed in search
|
||||||
pub fn is_searchable(&self) -> bool {
|
pub fn is_searchable(&self) -> bool {
|
||||||
matches!(self, ProjectStatus::Approved)
|
matches!(self, ProjectStatus::Approved | ProjectStatus::Archived)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project is "Approved" by moderators
|
||||||
|
pub fn is_approved(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
ProjectStatus::Approved
|
||||||
|
| ProjectStatus::Archived
|
||||||
|
| ProjectStatus::Unlisted
|
||||||
|
| ProjectStatus::Private
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project status can be requested after moderator approval
|
||||||
|
pub fn can_be_requested(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ProjectStatus::Approved => true,
|
||||||
|
ProjectStatus::Archived => true,
|
||||||
|
ProjectStatus::Unlisted => true,
|
||||||
|
ProjectStatus::Private => true,
|
||||||
|
ProjectStatus::Draft => true,
|
||||||
|
|
||||||
|
ProjectStatus::Rejected => false,
|
||||||
|
ProjectStatus::Processing => false,
|
||||||
|
ProjectStatus::Unknown => false,
|
||||||
|
ProjectStatus::Withheld => false,
|
||||||
|
ProjectStatus::Scheduled => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +409,10 @@ pub struct Version {
|
|||||||
pub downloads: u32,
|
pub downloads: u32,
|
||||||
/// The type of the release - `Alpha`, `Beta`, or `Release`.
|
/// The type of the release - `Alpha`, `Beta`, or `Release`.
|
||||||
pub version_type: VersionType,
|
pub version_type: VersionType,
|
||||||
|
/// The status of tne version
|
||||||
|
pub status: VersionStatus,
|
||||||
|
/// The requested status of the version (used for scheduling)
|
||||||
|
pub requested_status: Option<VersionStatus>,
|
||||||
|
|
||||||
/// A list of files available for download for this version.
|
/// A list of files available for download for this version.
|
||||||
pub files: Vec<VersionFile>,
|
pub files: Vec<VersionFile>,
|
||||||
@ -356,25 +426,29 @@ pub struct Version {
|
|||||||
|
|
||||||
impl From<QueryVersion> for Version {
|
impl From<QueryVersion> for Version {
|
||||||
fn from(data: QueryVersion) -> Version {
|
fn from(data: QueryVersion) -> Version {
|
||||||
Version {
|
let v = data.inner;
|
||||||
id: data.id.into(),
|
|
||||||
project_id: data.project_id.into(),
|
|
||||||
author_id: data.author_id.into(),
|
|
||||||
|
|
||||||
featured: data.featured,
|
Version {
|
||||||
name: data.name,
|
id: v.id.into(),
|
||||||
version_number: data.version_number,
|
project_id: v.project_id.into(),
|
||||||
changelog: data.changelog,
|
author_id: v.author_id.into(),
|
||||||
changelog_url: data.changelog_url,
|
|
||||||
date_published: data.date_published,
|
featured: v.featured,
|
||||||
downloads: data.downloads as u32,
|
name: v.name,
|
||||||
version_type: match data.version_type.as_str() {
|
version_number: v.version_number,
|
||||||
|
changelog: v.changelog,
|
||||||
|
changelog_url: v.changelog_url,
|
||||||
|
date_published: v.date_published,
|
||||||
|
downloads: v.downloads as u32,
|
||||||
|
version_type: match v.version_type.as_str() {
|
||||||
"release" => VersionType::Release,
|
"release" => VersionType::Release,
|
||||||
"beta" => VersionType::Beta,
|
"beta" => VersionType::Beta,
|
||||||
"alpha" => VersionType::Alpha,
|
"alpha" => VersionType::Alpha,
|
||||||
_ => VersionType::Release,
|
_ => VersionType::Release,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
status: v.status,
|
||||||
|
requested_status: v.requested_status,
|
||||||
files: data
|
files: data
|
||||||
.files
|
.files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -417,6 +491,88 @@ impl From<QueryVersion> for Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A status decides the visibility of a project in search, URLs, and the whole site itself.
|
||||||
|
/// Listed - Version is displayed on project, and accessible by URL
|
||||||
|
/// Draft - Version is not displayed on project, and not accessible by URL
|
||||||
|
/// Unlisted - Version is not displayed on project, and accessible by URL
|
||||||
|
/// Scheduled - Version is scheduled to be released in the future
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum VersionStatus {
|
||||||
|
Listed,
|
||||||
|
Draft,
|
||||||
|
Unlisted,
|
||||||
|
Scheduled,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VersionStatus {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(fmt, "{}", self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionStatus {
|
||||||
|
pub fn from_str(string: &str) -> VersionStatus {
|
||||||
|
match string {
|
||||||
|
"listed" => VersionStatus::Listed,
|
||||||
|
"draft" => VersionStatus::Draft,
|
||||||
|
"unlisted" => VersionStatus::Unlisted,
|
||||||
|
_ => VersionStatus::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
VersionStatus::Listed => "listed",
|
||||||
|
VersionStatus::Draft => "draft",
|
||||||
|
VersionStatus::Unlisted => "unlisted",
|
||||||
|
VersionStatus::Unknown => "unknown",
|
||||||
|
VersionStatus::Scheduled => "scheduled",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterator() -> impl Iterator<Item = VersionStatus> {
|
||||||
|
[
|
||||||
|
VersionStatus::Listed,
|
||||||
|
VersionStatus::Draft,
|
||||||
|
VersionStatus::Unlisted,
|
||||||
|
VersionStatus::Scheduled,
|
||||||
|
VersionStatus::Unknown,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version pages + info cannot be viewed
|
||||||
|
pub fn is_hidden(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
VersionStatus::Listed => false,
|
||||||
|
VersionStatus::Unlisted => false,
|
||||||
|
|
||||||
|
VersionStatus::Draft => true,
|
||||||
|
VersionStatus::Scheduled => true,
|
||||||
|
VersionStatus::Unknown => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether version is listed on project / returned in aggregate routes
|
||||||
|
pub fn is_listed(&self) -> bool {
|
||||||
|
matches!(self, VersionStatus::Listed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether a version status can be requested
|
||||||
|
pub fn can_be_requested(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
VersionStatus::Listed => true,
|
||||||
|
VersionStatus::Draft => true,
|
||||||
|
VersionStatus::Unlisted => true,
|
||||||
|
VersionStatus::Scheduled => false,
|
||||||
|
|
||||||
|
VersionStatus::Unknown => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A single project file, with a url for the file and the file's hash
|
/// A single project file, with a url for the file and the file's hash
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct VersionFile {
|
pub struct VersionFile {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use crate::database::models::project_item::QueryProject;
|
|||||||
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
use crate::database::models::version_item::{QueryFile, QueryVersion};
|
||||||
use crate::models::projects::{ProjectId, VersionId};
|
use crate::models::projects::{ProjectId, VersionId};
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::get_user_from_headers;
|
use crate::util::auth::{get_user_from_headers, is_authorized_version};
|
||||||
use crate::{database, util::auth::is_authorized};
|
use crate::{database, util::auth::is_authorized};
|
||||||
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
use actix_web::{get, route, web, HttpRequest, HttpResponse};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@ -58,8 +58,7 @@ pub async fn maven_metadata(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id = params.into_inner().0;
|
let project_id = params.into_inner().0;
|
||||||
let project_data =
|
let project_data = database::models::Project::get_from_slug_or_project_id(
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
|
||||||
&project_id,
|
&project_id,
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
@ -81,10 +80,14 @@ pub async fn maven_metadata(
|
|||||||
"
|
"
|
||||||
SELECT id, version_number, version_type
|
SELECT id, version_number, version_type
|
||||||
FROM versions
|
FROM versions
|
||||||
WHERE mod_id = $1
|
WHERE mod_id = $1 AND status = ANY($2)
|
||||||
ORDER BY date_published ASC
|
ORDER BY date_published ASC
|
||||||
",
|
",
|
||||||
data.inner.id as database::models::ids::ProjectId
|
data.id as database::models::ids::ProjectId,
|
||||||
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_listed())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&**pool)
|
.fetch_all(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -108,7 +111,7 @@ pub async fn maven_metadata(
|
|||||||
new_versions.push(value);
|
new_versions.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_id: ProjectId = data.inner.id.into();
|
let project_id: ProjectId = data.id.into();
|
||||||
|
|
||||||
let respdata = Metadata {
|
let respdata = Metadata {
|
||||||
group_id: "maven.modrinth".to_string(),
|
group_id: "maven.modrinth".to_string(),
|
||||||
@ -122,7 +125,7 @@ pub async fn maven_metadata(
|
|||||||
versions: Versions {
|
versions: Versions {
|
||||||
versions: new_versions,
|
versions: new_versions,
|
||||||
},
|
},
|
||||||
last_updated: data.inner.updated.format("%Y%m%d%H%M%S").to_string(),
|
last_updated: data.updated.format("%Y%m%d%H%M%S").to_string(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,10 +152,13 @@ fn find_file<'a>(
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let version_id: VersionId = version.id.into();
|
let version_id: VersionId = version.inner.id.into();
|
||||||
|
|
||||||
if file
|
if file
|
||||||
== format!("{}-{}.{}", &project_id, &version.version_number, fileext)
|
== format!(
|
||||||
|
"{}-{}.{}",
|
||||||
|
&project_id, &version.inner.version_number, fileext
|
||||||
|
)
|
||||||
|| file == format!("{}-{}.{}", &project_id, &version_id, fileext)
|
|| file == format!("{}-{}.{}", &project_id, &version_id, fileext)
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
@ -191,7 +197,7 @@ pub async fn version_file(
|
|||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if !is_authorized(&project, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,8 +230,12 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version_id: VersionId = version.id.into();
|
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
|
||||||
if file == format!("{}-{}.pom", &project_id, &version.version_number)
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
let version_id: VersionId = version.inner.id.into();
|
||||||
|
if file == format!("{}-{}.pom", &project_id, &version.inner.version_number)
|
||||||
|| file == format!("{}-{}.pom", &project_id, version_id)
|
|| file == format!("{}-{}.pom", &project_id, version_id)
|
||||||
{
|
{
|
||||||
let respdata = MavenPom {
|
let respdata = MavenPom {
|
||||||
@ -276,7 +286,7 @@ pub async fn version_file_sha1(
|
|||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if !is_authorized(&project, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +348,7 @@ pub async fn version_file_sha512(
|
|||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if !is_authorized(&project, &user_option, &pool).await? {
|
if !is_authorized(&project.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,6 +381,10 @@ pub async fn version_file_sha512(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
|
||||||
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(find_file(&project_id, &project, &version, &file)
|
Ok(find_file(&project_id, &project, &version, &file)
|
||||||
.and_then(|file| file.hashes.get("sha512"))
|
.and_then(|file| file.hashes.get("sha512"))
|
||||||
.and_then(|hash_bytes| std::str::from_utf8(hash_bytes).ok())
|
.and_then(|hash_bytes| std::str::from_utf8(hash_bytes).ok())
|
||||||
|
|||||||
@ -67,6 +67,7 @@ pub fn projects_config(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(projects::delete_gallery_item)
|
.service(projects::delete_gallery_item)
|
||||||
.service(projects::project_follow)
|
.service(projects::project_follow)
|
||||||
.service(projects::project_unfollow)
|
.service(projects::project_unfollow)
|
||||||
|
.service(projects::project_schedule)
|
||||||
.service(teams::team_members_get_project)
|
.service(teams::team_members_get_project)
|
||||||
.service(
|
.service(
|
||||||
web::scope("{project_id}")
|
web::scope("{project_id}")
|
||||||
@ -95,7 +96,8 @@ pub fn versions_config(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(versions::version_get)
|
.service(versions::version_get)
|
||||||
.service(versions::version_delete)
|
.service(versions::version_delete)
|
||||||
.service(version_creation::upload_file_to_version)
|
.service(version_creation::upload_file_to_version)
|
||||||
.service(versions::version_edit),
|
.service(versions::version_edit)
|
||||||
|
.service(versions::version_schedule),
|
||||||
);
|
);
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("version_file")
|
web::scope("version_file")
|
||||||
|
|||||||
@ -29,9 +29,7 @@ pub async fn get_projects(
|
|||||||
let project_ids = sqlx::query!(
|
let project_ids = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id FROM mods
|
SELECT id FROM mods
|
||||||
WHERE status = (
|
WHERE status = $1
|
||||||
SELECT id FROM statuses WHERE status = $1
|
|
||||||
)
|
|
||||||
ORDER BY updated ASC
|
ORDER BY updated ASC
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
",
|
",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::file_hosting::{FileHost, FileHostingError};
|
|||||||
use crate::models::error::ApiError;
|
use crate::models::error::ApiError;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
DonationLink, License, ProjectId, ProjectStatus, SideType, VersionId,
|
DonationLink, License, ProjectId, ProjectStatus, SideType, VersionId,
|
||||||
|
VersionStatus,
|
||||||
};
|
};
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
use crate::queue::flameanvil::FlameAnvilQueue;
|
use crate::queue::flameanvil::FlameAnvilQueue;
|
||||||
@ -131,6 +132,10 @@ fn default_project_type() -> String {
|
|||||||
"mod".to_string()
|
"mod".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_requested_status() -> ProjectStatus {
|
||||||
|
ProjectStatus::Approved
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||||
struct ProjectCreateData {
|
struct ProjectCreateData {
|
||||||
#[validate(length(min = 3, max = 64))]
|
#[validate(length(min = 3, max = 64))]
|
||||||
@ -218,6 +223,9 @@ struct ProjectCreateData {
|
|||||||
#[validate]
|
#[validate]
|
||||||
/// The multipart names of the gallery items to upload
|
/// The multipart names of the gallery items to upload
|
||||||
pub gallery_items: Option<Vec<NewGalleryItem>>,
|
pub gallery_items: Option<Vec<NewGalleryItem>>,
|
||||||
|
#[serde(default = "default_requested_status")]
|
||||||
|
/// The status of the mod to be set once it is approved
|
||||||
|
pub requested_status: ProjectStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||||
@ -658,14 +666,12 @@ pub async fn project_create_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_id = models::StatusId::get_id(&status, &mut *transaction)
|
if !project_create_data.requested_status.can_be_requested() {
|
||||||
.await?
|
return Err(CreateError::InvalidInput(String::from(
|
||||||
.ok_or_else(|| {
|
"Specified requested status is not allowed to be requested",
|
||||||
CreateError::InvalidInput(format!(
|
)));
|
||||||
"Status {} does not exist.",
|
}
|
||||||
status.clone()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let client_side_id = models::SideTypeId::get_id(
|
let client_side_id = models::SideTypeId::get_id(
|
||||||
&project_create_data.client_side,
|
&project_create_data.client_side,
|
||||||
&mut *transaction,
|
&mut *transaction,
|
||||||
@ -741,7 +747,8 @@ pub async fn project_create_inner(
|
|||||||
categories,
|
categories,
|
||||||
additional_categories,
|
additional_categories,
|
||||||
initial_versions: versions,
|
initial_versions: versions,
|
||||||
status: status_id,
|
status,
|
||||||
|
requested_status: Some(project_create_data.requested_status),
|
||||||
client_side: client_side_id,
|
client_side: client_side_id,
|
||||||
server_side: server_side_id,
|
server_side: server_side_id,
|
||||||
license: license_id.to_string(),
|
license: license_id.to_string(),
|
||||||
@ -774,7 +781,8 @@ pub async fn project_create_inner(
|
|||||||
published: now,
|
published: now,
|
||||||
updated: now,
|
updated: now,
|
||||||
approved: None,
|
approved: None,
|
||||||
status: status.clone(),
|
status,
|
||||||
|
requested_status: project_builder.requested_status,
|
||||||
moderator_message: None,
|
moderator_message: None,
|
||||||
license: License {
|
license: License {
|
||||||
id: project_create_data.license_id.clone(),
|
id: project_create_data.license_id.clone(),
|
||||||
@ -895,7 +903,9 @@ async fn create_initial_version(
|
|||||||
game_versions,
|
game_versions,
|
||||||
loaders,
|
loaders,
|
||||||
featured: version_data.featured,
|
featured: version_data.featured,
|
||||||
|
status: VersionStatus::Listed,
|
||||||
version_type: version_data.release_channel.to_string(),
|
version_type: version_data.release_channel.to_string(),
|
||||||
|
requested_status: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(version)
|
Ok(version)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use crate::util::auth::{get_user_from_headers, is_authorized};
|
|||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Utc;
|
use chrono::{DateTime, Utc};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -52,7 +52,7 @@ pub async fn projects_get(
|
|||||||
|
|
||||||
let projects: Vec<_> = futures::stream::iter(projects_data)
|
let projects: Vec<_> = futures::stream::iter(projects_data)
|
||||||
.filter_map(|data| async {
|
.filter_map(|data| async {
|
||||||
if is_authorized(&data, &user_option, &pool).await.ok()? {
|
if is_authorized(&data.inner, &user_option, &pool).await.ok()? {
|
||||||
Some(Project::from(data))
|
Some(Project::from(data))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -81,7 +81,7 @@ pub async fn project_get(
|
|||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(data) = project_data {
|
if let Some(data) = project_data {
|
||||||
if is_authorized(&data, &user_option, &pool).await? {
|
if is_authorized(&data.inner, &user_option, &pool).await? {
|
||||||
return Ok(HttpResponse::Ok().json(Project::from(data)));
|
return Ok(HttpResponse::Ok().json(Project::from(data)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ pub async fn dependency_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_full_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(
|
||||||
&string, &**pool,
|
&string, &**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -171,7 +171,7 @@ pub async fn dependency_list(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = project.inner.id;
|
let id = project.id;
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
@ -333,6 +333,12 @@ pub struct EditProject {
|
|||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
with = "::serde_with::rust::double_option"
|
with = "::serde_with::rust::double_option"
|
||||||
)]
|
)]
|
||||||
|
pub requested_status: Option<Option<ProjectStatus>>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
with = "::serde_with::rust::double_option"
|
||||||
|
)]
|
||||||
#[validate(length(max = 2000))]
|
#[validate(length(max = 2000))]
|
||||||
pub moderation_message: Option<Option<String>>,
|
pub moderation_message: Option<Option<String>>,
|
||||||
#[serde(
|
#[serde(
|
||||||
@ -451,12 +457,11 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == &ProjectStatus::Rejected
|
if (status.is_approved() || !status.can_be_requested())
|
||||||
|| status == &ProjectStatus::Approved)
|
|
||||||
&& !user.role.is_mod()
|
&& !user.role.is_mod()
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to set this status"
|
"You don't have permission to set this status!"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -502,20 +507,7 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_id = database::models::StatusId::get_id(
|
if status.is_approved() {
|
||||||
status,
|
|
||||||
&mut *transaction,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::InvalidInput(
|
|
||||||
"No database entry for status provided.".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if status == &ProjectStatus::Approved
|
|
||||||
|| status == &ProjectStatus::Unlisted
|
|
||||||
{
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE mods
|
UPDATE mods
|
||||||
@ -534,19 +526,49 @@ pub async fn project_edit(
|
|||||||
SET status = $1
|
SET status = $1
|
||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
status_id as database::models::ids::StatusId,
|
status.as_str(),
|
||||||
id as database::models::ids::ProjectId,
|
id as database::models::ids::ProjectId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if project_item.status.is_searchable()
|
if project_item.inner.status.is_searchable()
|
||||||
&& !status.is_searchable()
|
&& !status.is_searchable()
|
||||||
{
|
{
|
||||||
delete_from_index(id.into(), config).await?;
|
delete_from_index(id.into(), config).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(requested_status) = &new_project.requested_status {
|
||||||
|
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||||
|
return Err(ApiError::CustomAuthentication(
|
||||||
|
"You do not have the permissions to edit the requested status of this project!"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !requested_status
|
||||||
|
.map(|x| x.can_be_requested())
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
return Err(ApiError::InvalidInput(String::from(
|
||||||
|
"Specified status cannot be requested!",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods
|
||||||
|
SET requested_status = $1
|
||||||
|
WHERE (id = $2)
|
||||||
|
",
|
||||||
|
requested_status.map(|x| x.as_str()),
|
||||||
|
id as database::models::ids::ProjectId,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
if perms.contains(Permissions::EDIT_DETAILS) {
|
if perms.contains(Permissions::EDIT_DETAILS) {
|
||||||
if new_project.categories.is_some() {
|
if new_project.categories.is_some() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -863,7 +885,7 @@ pub async fn project_edit(
|
|||||||
license = models::projects::DEFAULT_LICENSE_ID.to_string();
|
license = models::projects::DEFAULT_LICENSE_ID.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
spdx::Expression::parse(&*license).map_err(|err| {
|
spdx::Expression::parse(&license).map_err(|err| {
|
||||||
ApiError::InvalidInput(format!(
|
ApiError::InvalidInput(format!(
|
||||||
"Invalid SPDX license identifier: {}",
|
"Invalid SPDX license identifier: {}",
|
||||||
err
|
err
|
||||||
@ -931,7 +953,7 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
if let Some(moderation_message) = &new_project.moderation_message {
|
if let Some(moderation_message) = &new_project.moderation_message {
|
||||||
if !user.role.is_mod()
|
if !user.role.is_mod()
|
||||||
&& project_item.status != ProjectStatus::Approved
|
&& project_item.inner.status != ProjectStatus::Approved
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the moderation message of this project!"
|
"You do not have the permissions to edit the moderation message of this project!"
|
||||||
@ -956,7 +978,7 @@ pub async fn project_edit(
|
|||||||
&new_project.moderation_message_body
|
&new_project.moderation_message_body
|
||||||
{
|
{
|
||||||
if !user.role.is_mod()
|
if !user.role.is_mod()
|
||||||
&& project_item.status != ProjectStatus::Approved
|
&& project_item.inner.status != ProjectStatus::Approved
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the moderation message body of this project!"
|
"You do not have the permissions to edit the moderation message body of this project!"
|
||||||
@ -1096,6 +1118,77 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SchedulingData {
|
||||||
|
pub time: DateTime<Utc>,
|
||||||
|
pub requested_status: ProjectStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("{id}/schedule")]
|
||||||
|
pub async fn project_schedule(
|
||||||
|
req: HttpRequest,
|
||||||
|
info: web::Path<(String,)>,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
|
if scheduling_data.time < Utc::now() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"You cannot schedule a project to be released in the past!"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !scheduling_data.requested_status.can_be_requested() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Specified requested status cannot be requested!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = info.into_inner().0;
|
||||||
|
let result = database::models::Project::get_from_slug_or_project_id(
|
||||||
|
&string, &**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(project_item) = result {
|
||||||
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
|
project_item.team_id,
|
||||||
|
user.id.into(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if user.role.is_mod()
|
||||||
|
|| team_member
|
||||||
|
.map(|x| x.permissions.contains(Permissions::EDIT_DETAILS))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Err(ApiError::CustomAuthentication(
|
||||||
|
"You do not have permission to edit this project's scheduling data!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods
|
||||||
|
SET status = $1, approved = $2
|
||||||
|
WHERE (id = $3)
|
||||||
|
",
|
||||||
|
ProjectStatus::Scheduled.as_str(),
|
||||||
|
scheduling_data.time,
|
||||||
|
project_item.id as database::models::ids::ProjectId,
|
||||||
|
)
|
||||||
|
.execute(&**pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
} else {
|
||||||
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Extension {
|
pub struct Extension {
|
||||||
pub ext: String,
|
pub ext: String,
|
||||||
|
|||||||
@ -11,12 +11,12 @@ pub async fn get_stats(
|
|||||||
"
|
"
|
||||||
SELECT COUNT(id)
|
SELECT COUNT(id)
|
||||||
FROM mods
|
FROM mods
|
||||||
WHERE
|
WHERE status = ANY($1)
|
||||||
status = ( SELECT id FROM statuses WHERE status = $1 ) OR
|
|
||||||
status = ( SELECT id FROM statuses WHERE status = $2 )
|
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str()
|
.filter(|x| x.is_searchable())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_one(&**pool);
|
.fetch_one(&**pool);
|
||||||
|
|
||||||
@ -24,13 +24,17 @@ pub async fn get_stats(
|
|||||||
"
|
"
|
||||||
SELECT COUNT(v.id)
|
SELECT COUNT(v.id)
|
||||||
FROM versions v
|
FROM versions v
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1)
|
||||||
WHERE
|
WHERE v.status = ANY($2)
|
||||||
status = ( SELECT id FROM statuses WHERE status = $1 ) OR
|
|
||||||
status = ( SELECT id FROM statuses WHERE status = $2 )
|
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str()
|
.filter(|x| x.is_searchable())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_listed())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_one(&**pool);
|
.fetch_one(&**pool);
|
||||||
|
|
||||||
@ -39,27 +43,29 @@ pub async fn get_stats(
|
|||||||
SELECT COUNT(DISTINCT u.id)
|
SELECT COUNT(DISTINCT u.id)
|
||||||
FROM users u
|
FROM users u
|
||||||
INNER JOIN team_members tm on u.id = tm.user_id AND tm.accepted = TRUE
|
INNER JOIN team_members tm on u.id = tm.user_id AND tm.accepted = TRUE
|
||||||
INNER JOIN mods m on tm.team_id = m.team_id AND (
|
INNER JOIN mods m on tm.team_id = m.team_id AND m.status = ANY($1)
|
||||||
m.status = ( SELECT s.id FROM statuses s WHERE s.status = $1 ) OR
|
|
||||||
m.status = ( SELECT s.id FROM statuses s WHERE s.status = $2 )
|
|
||||||
)
|
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str()
|
.filter(|x| x.is_searchable())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_one(&**pool);
|
.fetch_one(&**pool);
|
||||||
|
|
||||||
let files = sqlx::query!(
|
let files = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT COUNT(f.id) FROM files f
|
SELECT COUNT(f.id) FROM files f
|
||||||
INNER JOIN versions v on f.version_id = v.id
|
INNER JOIN versions v on f.version_id = v.id AND v.status = ANY($2)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1)
|
||||||
WHERE
|
|
||||||
status = ( SELECT id FROM statuses WHERE status = $1 ) OR
|
|
||||||
status = ( SELECT id FROM statuses WHERE status = $2 )
|
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str()
|
.filter(|x| x.is_searchable())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_listed())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_one(&**pool);
|
.fetch_one(&**pool);
|
||||||
|
|
||||||
|
|||||||
@ -332,13 +332,13 @@ pub async fn license_text(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let license_id = params.into_inner().0;
|
let license_id = params.into_inner().0;
|
||||||
|
|
||||||
if license_id == crate::models::projects::DEFAULT_LICENSE_ID.to_string() {
|
if license_id == *crate::models::projects::DEFAULT_LICENSE_ID {
|
||||||
return Ok(HttpResponse::Ok().json(LicenseText {
|
return Ok(HttpResponse::Ok().json(LicenseText {
|
||||||
body: "All rights reserved unless explicitly stated.".to_string(),
|
body: "All rights reserved unless explicitly stated.".to_string(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(license) = spdx::license_id(&*license_id) {
|
if let Some(license) = spdx::license_id(&license_id) {
|
||||||
return Ok(HttpResponse::Ok().json(LicenseText {
|
return Ok(HttpResponse::Ok().json(LicenseText {
|
||||||
body: license.text().to_string(),
|
body: license.text().to_string(),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -6,7 +6,10 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::projects::{Version, VersionType};
|
use crate::models::projects::{Version, VersionType};
|
||||||
use crate::util::auth::{get_user_from_headers, is_authorized};
|
use crate::util::auth::{
|
||||||
|
get_user_from_headers, is_authorized, is_authorized_version,
|
||||||
|
};
|
||||||
|
use futures::StreamExt;
|
||||||
|
|
||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
|
|
||||||
@ -20,9 +23,8 @@ pub async fn forge_updates(
|
|||||||
|
|
||||||
let (id,) = info.into_inner();
|
let (id,) = info.into_inner();
|
||||||
|
|
||||||
let project = database::models::Project::get_full_from_slug_or_project_id(
|
let project =
|
||||||
&id, &**pool,
|
database::models::Project::get_from_slug_or_project_id(&id, &**pool)
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||||
|
|
||||||
@ -33,16 +35,32 @@ pub async fn forge_updates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let version_ids = database::models::Version::get_project_versions(
|
let version_ids = database::models::Version::get_project_versions(
|
||||||
project.inner.id,
|
project.id,
|
||||||
None,
|
None,
|
||||||
Some(vec!["forge".to_string()]),
|
Some(vec!["forge".to_string()]),
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut versions =
|
let versions =
|
||||||
database::models::Version::get_many_full(version_ids, &**pool).await?;
|
database::models::Version::get_many_full(version_ids, &**pool).await?;
|
||||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
|
||||||
|
let mut versions = futures::stream::iter(versions)
|
||||||
|
.filter_map(|data| async {
|
||||||
|
if is_authorized_version(&data.inner, &user_option, &pool)
|
||||||
|
.await
|
||||||
|
.ok()?
|
||||||
|
{
|
||||||
|
Some(data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
versions
|
||||||
|
.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ForgeUpdates {
|
struct ForgeUpdates {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
use crate::models::projects::{Project, ProjectStatus};
|
use crate::models::projects::Project;
|
||||||
use crate::models::users::{
|
use crate::models::users::{
|
||||||
Badges, RecipientType, RecipientWallet, Role, UserId,
|
Badges, RecipientType, RecipientWallet, Role, UserId,
|
||||||
};
|
};
|
||||||
@ -91,35 +91,23 @@ pub async fn projects_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
let id_option =
|
||||||
&info.into_inner().0,
|
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
let user_id: UserId = id.into();
|
||||||
|
|
||||||
let project_data = if let Some(current_user) = user {
|
let can_view_private = user
|
||||||
if current_user.role.is_mod() || current_user.id == user_id {
|
.map(|y| y.role.is_mod() || y.id == user_id)
|
||||||
User::get_projects_private(id, &**pool).await?
|
.unwrap_or(false);
|
||||||
} else {
|
|
||||||
User::get_projects(
|
let project_data = User::get_projects(id, &**pool).await?;
|
||||||
id,
|
|
||||||
ProjectStatus::Approved.as_str(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let response: Vec<_> =
|
let response: Vec<_> =
|
||||||
crate::database::Project::get_many_full(project_data, &**pool)
|
crate::database::Project::get_many_full(project_data, &**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|x| can_view_private || x.inner.status.is_approved())
|
||||||
.map(Project::from)
|
.map(Project::from)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,7 @@ pub async fn get_mods(
|
|||||||
let project_ids = sqlx::query!(
|
let project_ids = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id FROM mods
|
SELECT id FROM mods
|
||||||
WHERE status = (
|
WHERE status = $1
|
||||||
SELECT id FROM statuses WHERE status = $1
|
|
||||||
)
|
|
||||||
ORDER BY updated ASC
|
ORDER BY updated ASC
|
||||||
LIMIT $2;
|
LIMIT $2;
|
||||||
",
|
",
|
||||||
|
|||||||
@ -108,7 +108,7 @@ pub async fn mods_get(
|
|||||||
|
|
||||||
// can't use `map` and `collect` here since `is_authorized` must be async
|
// can't use `map` and `collect` here since `is_authorized` must be async
|
||||||
for proj in projects_data {
|
for proj in projects_data {
|
||||||
if is_authorized(&proj, &user_option, &pool).await? {
|
if is_authorized(&proj.inner, &user_option, &pool).await? {
|
||||||
projects.push(crate::models::projects::Project::from(proj))
|
projects.push(crate::models::projects::Project::from(proj))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::models::ids::UserId;
|
use crate::models::ids::UserId;
|
||||||
use crate::models::projects::{ProjectId, ProjectStatus};
|
use crate::models::projects::ProjectId;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::get_user_from_headers;
|
use crate::util::auth::get_user_from_headers;
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
@ -24,26 +24,19 @@ pub async fn mods_list(
|
|||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
let user_id: UserId = id.into();
|
||||||
|
|
||||||
let project_data = if let Some(current_user) = user {
|
let can_view_private = user
|
||||||
if current_user.role.is_mod() || current_user.id == user_id {
|
.map(|y| y.role.is_mod() || y.id == user_id)
|
||||||
User::get_projects_private(id, &**pool).await?
|
.unwrap_or(false);
|
||||||
} else {
|
|
||||||
User::get_projects(
|
|
||||||
id,
|
|
||||||
ProjectStatus::Approved.as_str(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = project_data
|
let project_data = User::get_projects(id, &**pool).await?;
|
||||||
|
|
||||||
|
let response: Vec<_> =
|
||||||
|
crate::database::Project::get_many(project_data, &**pool)
|
||||||
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| v.into())
|
.filter(|x| can_view_private || x.status.is_approved())
|
||||||
.collect::<Vec<crate::models::ids::ProjectId>>();
|
.map(|x| x.id.into())
|
||||||
|
.collect::<Vec<ProjectId>>();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -92,14 +92,16 @@ pub async fn version_list(
|
|||||||
.filter(|version| {
|
.filter(|version| {
|
||||||
filters
|
filters
|
||||||
.featured
|
.featured
|
||||||
.map(|featured| featured == version.featured)
|
.map(|featured| featured == version.inner.featured)
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
})
|
})
|
||||||
.map(Version::from)
|
.map(Version::from)
|
||||||
.map(convert_to_legacy)
|
.map(convert_to_legacy)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
versions.sort_by(|a, b| {
|
||||||
|
b.inner.date_published.cmp(&a.inner.date_published)
|
||||||
|
});
|
||||||
|
|
||||||
// Attempt to populate versions with "auto featured" versions
|
// Attempt to populate versions with "auto featured" versions
|
||||||
if response.is_empty()
|
if response.is_empty()
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::file_hosting::FileHost;
|
|||||||
use crate::models::pack::PackFileHash;
|
use crate::models::pack::PackFileHash;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
Dependency, DependencyType, GameVersion, Loader, ProjectId, Version,
|
Dependency, DependencyType, GameVersion, Loader, ProjectId, Version,
|
||||||
VersionFile, VersionId, VersionType,
|
VersionFile, VersionId, VersionStatus, VersionType,
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::queue::flameanvil::{FlameAnvilQueue, UploadFile};
|
use crate::queue::flameanvil::{FlameAnvilQueue, UploadFile};
|
||||||
@ -59,6 +59,7 @@ pub struct InitialVersionData {
|
|||||||
pub loaders: Vec<Loader>,
|
pub loaders: Vec<Loader>,
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
pub primary_file: Option<String>,
|
pub primary_file: Option<String>,
|
||||||
|
pub status: VersionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
@ -160,6 +161,12 @@ async fn version_create_inner(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
if !version_create_data.status.can_be_requested() {
|
||||||
|
return Err(CreateError::InvalidInput(
|
||||||
|
"Status specified cannot be requested".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let project_id: models::ProjectId =
|
let project_id: models::ProjectId =
|
||||||
version_create_data.project_id.unwrap().into();
|
version_create_data.project_id.unwrap().into();
|
||||||
|
|
||||||
@ -274,6 +281,8 @@ async fn version_create_inner(
|
|||||||
loaders,
|
loaders,
|
||||||
version_type: version_create_data.release_channel.to_string(),
|
version_type: version_create_data.release_channel.to_string(),
|
||||||
featured: version_create_data.featured,
|
featured: version_create_data.featured,
|
||||||
|
status: version_create_data.status,
|
||||||
|
requested_status: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -415,6 +424,8 @@ async fn version_create_inner(
|
|||||||
date_published: Utc::now(),
|
date_published: Utc::now(),
|
||||||
downloads: 0,
|
downloads: 0,
|
||||||
version_type: version_data.release_channel,
|
version_type: version_data.release_channel,
|
||||||
|
status: builder.status,
|
||||||
|
requested_status: builder.requested_status,
|
||||||
files: builder
|
files: builder
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
@ -449,8 +460,6 @@ async fn version_create_inner(
|
|||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: file deletion, listing, etc
|
|
||||||
|
|
||||||
// under /api/v1/version/{version_id}
|
// under /api/v1/version/{version_id}
|
||||||
#[post("{version_id}/file")]
|
#[post("{version_id}/file")]
|
||||||
pub async fn upload_file_to_version(
|
pub async fn upload_file_to_version(
|
||||||
@ -551,7 +560,7 @@ async fn upload_file_to_version_inner(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_id = ProjectId(version.project_id.0 as u64);
|
let project_id = ProjectId(version.inner.project_id.0 as u64);
|
||||||
|
|
||||||
let project_type = sqlx::query!(
|
let project_type = sqlx::query!(
|
||||||
"
|
"
|
||||||
@ -559,7 +568,7 @@ async fn upload_file_to_version_inner(
|
|||||||
INNER JOIN mods ON mods.project_type = pt.id
|
INNER JOIN mods ON mods.project_type = pt.id
|
||||||
WHERE mods.id = $1
|
WHERE mods.id = $1
|
||||||
",
|
",
|
||||||
version.project_id as models::ProjectId,
|
version.inner.project_id as models::ProjectId,
|
||||||
)
|
)
|
||||||
.fetch_one(&mut *transaction)
|
.fetch_one(&mut *transaction)
|
||||||
.await?
|
.await?
|
||||||
@ -628,9 +637,9 @@ async fn upload_file_to_version_inner(
|
|||||||
all_game_versions.clone(),
|
all_game_versions.clone(),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
version.name.clone(),
|
version.inner.name.clone(),
|
||||||
version.changelog.clone(),
|
version.inner.changelog.clone(),
|
||||||
version.version_type.clone(),
|
version.inner.version_type.clone(),
|
||||||
flame_anvil_queue,
|
flame_anvil_queue,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@ -35,14 +35,20 @@ pub async fn get_version_from_hash(
|
|||||||
SELECT f.version_id version_id
|
SELECT f.version_id version_id
|
||||||
FROM hashes h
|
FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v on f.version_id = v.id
|
INNER JOIN versions v on f.version_id = v.id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hash.as_bytes(),
|
hash.as_bytes(),
|
||||||
algorithm.algorithm,
|
algorithm.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_optional(&**pool)
|
.fetch_optional(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -83,14 +89,14 @@ pub async fn download_version(
|
|||||||
"
|
"
|
||||||
SELECT f.url url, f.id id, f.version_id version_id, v.mod_id project_id FROM hashes h
|
SELECT f.url url, f.id id, f.version_id version_id, v.mod_id project_id FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
hash.as_bytes(),
|
hash.as_bytes(),
|
||||||
algorithm.algorithm,
|
algorithm.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -234,14 +240,20 @@ pub async fn get_update_from_hash(
|
|||||||
"
|
"
|
||||||
SELECT v.mod_id project_id FROM hashes h
|
SELECT v.mod_id project_id FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hash.as_bytes(),
|
hash.as_bytes(),
|
||||||
algorithm.algorithm,
|
algorithm.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_optional(&**pool)
|
.fetch_optional(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -306,14 +318,14 @@ pub async fn get_versions_from_hashes(
|
|||||||
"
|
"
|
||||||
SELECT h.hash hash, h.algorithm algorithm, f.version_id version_id FROM hashes h
|
SELECT h.hash hash, h.algorithm algorithm, f.version_id version_id FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
file_data.algorithm,
|
file_data.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&**pool)
|
.fetch_all(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@ -333,7 +345,7 @@ pub async fn get_versions_from_hashes(
|
|||||||
versions_data
|
versions_data
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|x| x.id.0 == row.version_id)
|
.find(|x| x.inner.id.0 == row.version_id)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
||||||
Ok((
|
Ok((
|
||||||
@ -369,14 +381,14 @@ pub async fn download_files(
|
|||||||
"
|
"
|
||||||
SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h
|
SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
file_data.algorithm,
|
file_data.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&mut *transaction)
|
.fetch_all(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -423,14 +435,14 @@ pub async fn update_files(
|
|||||||
"
|
"
|
||||||
SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h
|
SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h
|
||||||
INNER JOIN files f ON h.file_id = f.id
|
INNER JOIN files f ON h.file_id = f.id
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
INNER JOIN statuses s on m.status = s.id
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3
|
|
||||||
",
|
",
|
||||||
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
update_data.algorithm,
|
update_data.algorithm,
|
||||||
models::projects::ProjectStatus::Rejected.to_string()
|
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&mut *transaction)
|
.fetch_all(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -482,7 +494,7 @@ pub async fn update_files(
|
|||||||
let mut response = HashMap::new();
|
let mut response = HashMap::new();
|
||||||
|
|
||||||
for version in versions {
|
for version in versions {
|
||||||
let hash = version_ids.get(&version.id);
|
let hash = version_ids.get(&version.inner.id);
|
||||||
|
|
||||||
if let Some(hash) = hash {
|
if let Some(hash) = hash {
|
||||||
if let Ok(parsed_hash) = String::from_utf8(hash.clone()) {
|
if let Ok(parsed_hash) = String::from_utf8(hash.clone()) {
|
||||||
@ -491,7 +503,8 @@ pub async fn update_files(
|
|||||||
models::projects::Version::from(version),
|
models::projects::Version::from(version),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let version_id: models::projects::VersionId = version.id.into();
|
let version_id: models::projects::VersionId =
|
||||||
|
version.inner.id.into();
|
||||||
|
|
||||||
return Err(ApiError::Database(DatabaseError::Other(format!(
|
return Err(ApiError::Database(DatabaseError::Other(format!(
|
||||||
"Could not parse hash for version {}",
|
"Could not parse hash for version {}",
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::projects::{Dependency, Version};
|
use crate::models::projects::{Dependency, Version, VersionStatus};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::util::auth::{get_user_from_headers, is_authorized};
|
use crate::util::auth::{
|
||||||
|
get_user_from_headers, is_authorized, is_authorized_version,
|
||||||
|
};
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use futures::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@ -26,7 +30,7 @@ pub async fn version_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_full_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(
|
||||||
&string, &**pool,
|
&string, &**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@ -38,7 +42,7 @@ pub async fn version_list(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = project.inner.id;
|
let id = project.id;
|
||||||
|
|
||||||
let version_ids = database::models::Version::get_project_versions(
|
let version_ids = database::models::Version::get_project_versions(
|
||||||
id,
|
id,
|
||||||
@ -58,19 +62,27 @@ pub async fn version_list(
|
|||||||
database::models::Version::get_many_full(version_ids, &**pool)
|
database::models::Version::get_many_full(version_ids, &**pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut response = versions
|
let mut response = futures::stream::iter(versions.clone())
|
||||||
.iter()
|
.filter_map(|data| async {
|
||||||
.cloned()
|
if is_authorized_version(&data.inner, &user_option, &pool)
|
||||||
.filter(|version| {
|
.await
|
||||||
filters
|
.ok()?
|
||||||
|
&& filters
|
||||||
.featured
|
.featured
|
||||||
.map(|featured| featured == version.featured)
|
.map(|featured| featured == data.inner.featured)
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
Some(Version::from(data))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.map(Version::from)
|
.collect::<Vec<_>>()
|
||||||
.collect::<Vec<_>>();
|
.await;
|
||||||
|
|
||||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
versions.sort_by(|a, b| {
|
||||||
|
b.inner.date_published.cmp(&a.inner.date_published)
|
||||||
|
});
|
||||||
|
|
||||||
// Attempt to populate versions with "auto featured" versions
|
// Attempt to populate versions with "auto featured" versions
|
||||||
if response.is_empty()
|
if response.is_empty()
|
||||||
@ -131,6 +143,7 @@ pub struct VersionIds {
|
|||||||
|
|
||||||
#[get("versions")]
|
#[get("versions")]
|
||||||
pub async fn versions_get(
|
pub async fn versions_get(
|
||||||
|
req: HttpRequest,
|
||||||
web::Query(ids): web::Query<VersionIds>,
|
web::Query(ids): web::Query<VersionIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
@ -142,15 +155,28 @@ pub async fn versions_get(
|
|||||||
let versions_data =
|
let versions_data =
|
||||||
database::models::Version::get_many_full(version_ids, &**pool).await?;
|
database::models::Version::get_many_full(version_ids, &**pool).await?;
|
||||||
|
|
||||||
let versions = versions_data
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
.into_iter()
|
|
||||||
.map(Version::from)
|
let versions: Vec<_> = futures::stream::iter(versions_data)
|
||||||
.collect::<Vec<_>>();
|
.filter_map(|data| async {
|
||||||
|
if is_authorized_version(&data.inner, &user_option, &pool)
|
||||||
|
.await
|
||||||
|
.ok()?
|
||||||
|
{
|
||||||
|
Some(Version::from(data))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(versions))
|
Ok(HttpResponse::Ok().json(versions))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("{version_id}")]
|
#[get("{version_id}")]
|
||||||
pub async fn version_get(
|
pub async fn version_get(
|
||||||
|
req: HttpRequest,
|
||||||
info: web::Path<(models::ids::VersionId,)>,
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
@ -158,13 +184,19 @@ pub async fn version_get(
|
|||||||
let version_data =
|
let version_data =
|
||||||
database::models::Version::get_full(id.into(), &**pool).await?;
|
database::models::Version::get_full(id.into(), &**pool).await?;
|
||||||
|
|
||||||
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data {
|
||||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(data)))
|
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||||
} else {
|
return Ok(
|
||||||
Ok(HttpResponse::NotFound().body(""))
|
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
pub struct EditVersion {
|
pub struct EditVersion {
|
||||||
#[validate(length(min = 1, max = 64))]
|
#[validate(length(min = 1, max = 64))]
|
||||||
@ -187,6 +219,7 @@ pub struct EditVersion {
|
|||||||
pub featured: Option<bool>,
|
pub featured: Option<bool>,
|
||||||
pub primary_file: Option<(String, String)>,
|
pub primary_file: Option<(String, String)>,
|
||||||
pub downloads: Option<u32>,
|
pub downloads: Option<u32>,
|
||||||
|
pub status: Option<VersionStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("{id}")]
|
#[patch("{id}")]
|
||||||
@ -209,14 +242,14 @@ pub async fn version_edit(
|
|||||||
|
|
||||||
if let Some(version_item) = result {
|
if let Some(version_item) = result {
|
||||||
let project_item = database::models::Project::get_full(
|
let project_item = database::models::Project::get_full(
|
||||||
version_item.project_id,
|
version_item.inner.project_id,
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let team_member =
|
let team_member =
|
||||||
database::models::TeamMember::get_from_user_id_version(
|
database::models::TeamMember::get_from_user_id_version(
|
||||||
version_item.id,
|
version_item.inner.id,
|
||||||
user.id.into(),
|
user.id.into(),
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
@ -310,7 +343,7 @@ pub async fn version_edit(
|
|||||||
|
|
||||||
for dependency in builders {
|
for dependency in builders {
|
||||||
dependency
|
dependency
|
||||||
.insert(version_item.id, &mut transaction)
|
.insert(version_item.inner.id, &mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,7 +514,7 @@ pub async fn version_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let diff = *downloads - (version_item.downloads as u32);
|
let diff = *downloads - (version_item.inner.downloads as u32);
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@ -490,7 +523,28 @@ pub async fn version_edit(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
diff as i32,
|
diff as i32,
|
||||||
version_item.project_id as database::models::ids::ProjectId,
|
version_item.inner.project_id
|
||||||
|
as database::models::ids::ProjectId,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(status) = &new_version.status {
|
||||||
|
if !status.can_be_requested() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"The requested status cannot be set!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE versions
|
||||||
|
SET status = $1
|
||||||
|
WHERE (id = $2)
|
||||||
|
",
|
||||||
|
status.as_str(),
|
||||||
|
id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -508,6 +562,76 @@ pub async fn version_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SchedulingData {
|
||||||
|
pub time: DateTime<Utc>,
|
||||||
|
pub requested_status: VersionStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("{id}/schedule")]
|
||||||
|
pub async fn version_schedule(
|
||||||
|
req: HttpRequest,
|
||||||
|
info: web::Path<(models::ids::VersionId,)>,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
scheduling_data: web::Json<SchedulingData>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
|
if scheduling_data.time < Utc::now() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"You cannot schedule a version to be released in the past!"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !scheduling_data.requested_status.can_be_requested() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Specified requested status cannot be requested!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = info.into_inner().0;
|
||||||
|
let result =
|
||||||
|
database::models::Version::get_full(string.into(), &**pool).await?;
|
||||||
|
|
||||||
|
if let Some(version_item) = result {
|
||||||
|
let team_member =
|
||||||
|
database::models::TeamMember::get_from_user_id_version(
|
||||||
|
version_item.inner.id,
|
||||||
|
user.id.into(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if user.role.is_mod()
|
||||||
|
|| team_member
|
||||||
|
.map(|x| x.permissions.contains(Permissions::EDIT_DETAILS))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Err(ApiError::CustomAuthentication(
|
||||||
|
"You do not have permission to edit this version's scheduling data!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE versions
|
||||||
|
SET status = $1, date_published = $2
|
||||||
|
WHERE (id = $3)
|
||||||
|
",
|
||||||
|
VersionStatus::Scheduled.as_str(),
|
||||||
|
scheduling_data.time,
|
||||||
|
version_item.inner.id as database::models::ids::VersionId,
|
||||||
|
)
|
||||||
|
.execute(&**pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
} else {
|
||||||
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[delete("{version_id}")]
|
#[delete("{version_id}")]
|
||||||
pub async fn version_delete(
|
pub async fn version_delete(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
|||||||
@ -10,13 +10,14 @@ pub async fn index_local(
|
|||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
) -> Result<Vec<UploadSearchProject>, IndexingError> {
|
) -> Result<Vec<UploadSearchProject>, IndexingError> {
|
||||||
info!("Indexing local projects!");
|
info!("Indexing local projects!");
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT m.id id, m.project_type project_type, 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.published published, m.approved approved, m.updated updated,
|
m.icon_url icon_url, m.published published, m.approved approved, m.updated updated,
|
||||||
m.team_id team_id, m.license license, m.slug slug,
|
m.team_id team_id, m.license license, m.slug slug, m.status status_name,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,
|
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
||||||
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
||||||
@ -24,23 +25,22 @@ pub async fn index_local(
|
|||||||
FROM mods m
|
FROM mods m
|
||||||
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
|
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 categories c ON mc.joining_category_id = c.id
|
||||||
LEFT OUTER JOIN versions v ON v.mod_id = m.id
|
LEFT OUTER JOIN versions v ON v.mod_id = m.id AND v.status != ANY($1)
|
||||||
LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id
|
LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id
|
||||||
LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id
|
LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id
|
||||||
LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id
|
LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id
|
||||||
LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id
|
LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id
|
||||||
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
INNER JOIN statuses s ON s.id = m.status
|
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE
|
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE
|
||||||
INNER JOIN users u ON tm.user_id = u.id
|
INNER JOIN users u ON tm.user_id = u.id
|
||||||
WHERE s.status = $1 OR s.status = $2
|
WHERE m.status = ANY($2)
|
||||||
GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id;
|
GROUP BY m.id, cs.id, ss.id, pt.id, u.id;
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str(),
|
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_searchable()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||||
crate::models::teams::OWNER_ROLE,
|
crate::models::teams::OWNER_ROLE,
|
||||||
)
|
)
|
||||||
.fetch_many(&pool)
|
.fetch_many(&pool)
|
||||||
@ -72,7 +72,7 @@ pub async fn index_local(
|
|||||||
|
|
||||||
let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into();
|
let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into();
|
||||||
|
|
||||||
let license = match m.license.split(" ").next() {
|
let license = match m.license.split(' ').next() {
|
||||||
Some(license) => license.to_string(),
|
Some(license) => license.to_string(),
|
||||||
None => m.license,
|
None => m.license,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::database::models;
|
use crate::database::{models, Project, Version};
|
||||||
use crate::database::models::project_item::QueryProject;
|
|
||||||
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
use crate::models::users::{Role, User, UserId, UserPayoutData};
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use actix_web::http::header::HeaderMap;
|
use actix_web::http::header::HeaderMap;
|
||||||
@ -132,7 +131,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_authorized(
|
pub async fn is_authorized(
|
||||||
project_data: &QueryProject,
|
project_data: &Project,
|
||||||
user_option: &Option<User>,
|
user_option: &Option<User>,
|
||||||
pool: &web::Data<PgPool>,
|
pool: &web::Data<PgPool>,
|
||||||
) -> Result<bool, ApiError> {
|
) -> Result<bool, ApiError> {
|
||||||
@ -147,7 +146,7 @@ pub async fn is_authorized(
|
|||||||
|
|
||||||
let project_exists = sqlx::query!(
|
let project_exists = sqlx::query!(
|
||||||
"SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2)",
|
"SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2)",
|
||||||
project_data.inner.team_id as database::models::ids::TeamId,
|
project_data.team_id as database::models::ids::TeamId,
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_one(&***pool)
|
.fetch_one(&***pool)
|
||||||
@ -158,5 +157,37 @@ pub async fn is_authorized(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(authorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_authorized_version(
|
||||||
|
version_data: &Version,
|
||||||
|
user_option: &Option<User>,
|
||||||
|
pool: &web::Data<PgPool>,
|
||||||
|
) -> Result<bool, ApiError> {
|
||||||
|
let mut authorized = !version_data.status.is_hidden();
|
||||||
|
|
||||||
|
if let Some(user) = &user_option {
|
||||||
|
if !authorized {
|
||||||
|
if user.role.is_mod() {
|
||||||
|
authorized = true;
|
||||||
|
} else {
|
||||||
|
let user_id: models::ids::UserId = user.id.into();
|
||||||
|
|
||||||
|
let version_exists = sqlx::query!(
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 WHERE m.id = $1)",
|
||||||
|
version_data.project_id as database::models::ids::ProjectId,
|
||||||
|
user_id as database::models::ids::UserId,
|
||||||
|
)
|
||||||
|
.fetch_one(&***pool)
|
||||||
|
.await?
|
||||||
|
.exists;
|
||||||
|
|
||||||
|
authorized = version_exists.unwrap_or(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(authorized)
|
Ok(authorized)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user