You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -1,11 +1,15 @@
|
||||
use super::ApiError;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::projects::{Dependency, Version};
|
||||
use crate::models::projects::{Dependency, Version, VersionStatus};
|
||||
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 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 sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
@@ -26,7 +30,7 @@ pub async fn version_list(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
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,
|
||||
)
|
||||
.await?;
|
||||
@@ -38,7 +42,7 @@ pub async fn version_list(
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
}
|
||||
|
||||
let id = project.inner.id;
|
||||
let id = project.id;
|
||||
|
||||
let version_ids = database::models::Version::get_project_versions(
|
||||
id,
|
||||
@@ -58,19 +62,27 @@ pub async fn version_list(
|
||||
database::models::Version::get_many_full(version_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let mut response = versions
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|version| {
|
||||
filters
|
||||
.featured
|
||||
.map(|featured| featured == version.featured)
|
||||
.unwrap_or(true)
|
||||
let mut response = futures::stream::iter(versions.clone())
|
||||
.filter_map(|data| async {
|
||||
if is_authorized_version(&data.inner, &user_option, &pool)
|
||||
.await
|
||||
.ok()?
|
||||
&& filters
|
||||
.featured
|
||||
.map(|featured| featured == data.inner.featured)
|
||||
.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
|
||||
if response.is_empty()
|
||||
@@ -131,6 +143,7 @@ pub struct VersionIds {
|
||||
|
||||
#[get("versions")]
|
||||
pub async fn versions_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<VersionIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -142,15 +155,28 @@ pub async fn versions_get(
|
||||
let versions_data =
|
||||
database::models::Version::get_many_full(version_ids, &**pool).await?;
|
||||
|
||||
let versions = versions_data
|
||||
.into_iter()
|
||||
.map(Version::from)
|
||||
.collect::<Vec<_>>();
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let versions: Vec<_> = futures::stream::iter(versions_data)
|
||||
.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))
|
||||
}
|
||||
|
||||
#[get("{version_id}")]
|
||||
pub async fn version_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -158,11 +184,17 @@ pub async fn version_get(
|
||||
let version_data =
|
||||
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 {
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
@@ -187,6 +219,7 @@ pub struct EditVersion {
|
||||
pub featured: Option<bool>,
|
||||
pub primary_file: Option<(String, String)>,
|
||||
pub downloads: Option<u32>,
|
||||
pub status: Option<VersionStatus>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
@@ -209,14 +242,14 @@ pub async fn version_edit(
|
||||
|
||||
if let Some(version_item) = result {
|
||||
let project_item = database::models::Project::get_full(
|
||||
version_item.project_id,
|
||||
version_item.inner.project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_version(
|
||||
version_item.id,
|
||||
version_item.inner.id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
@@ -310,7 +343,7 @@ pub async fn version_edit(
|
||||
|
||||
for dependency in builders {
|
||||
dependency
|
||||
.insert(version_item.id, &mut transaction)
|
||||
.insert(version_item.inner.id, &mut transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
@@ -481,7 +514,7 @@ pub async fn version_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let diff = *downloads - (version_item.downloads as u32);
|
||||
let diff = *downloads - (version_item.inner.downloads as u32);
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -490,7 +523,28 @@ pub async fn version_edit(
|
||||
WHERE (id = $2)
|
||||
",
|
||||
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)
|
||||
.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}")]
|
||||
pub async fn version_delete(
|
||||
req: HttpRequest,
|
||||
|
||||
Reference in New Issue
Block a user