forked from didirus/AstralRinth
Add dependencies to search (#578)
* Add dependencies to search * add attrs for faceting * run prepare * Add user data route from token * update to 24hrs * Fix report bugs
This commit is contained in:
@@ -68,11 +68,8 @@ pub async fn maven_metadata(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let project_id = params.into_inner().0;
|
||||
let project_data = database::models::Project::get_from_slug_or_project_id(
|
||||
&project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let project_data =
|
||||
database::models::Project::get_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||
|
||||
let data = if let Some(data) = project_data {
|
||||
data
|
||||
@@ -150,9 +147,7 @@ fn find_file<'a>(
|
||||
version: &'a QueryVersion,
|
||||
file: &str,
|
||||
) -> Option<&'a QueryFile> {
|
||||
if let Some(selected_file) =
|
||||
version.files.iter().find(|x| x.filename == file)
|
||||
{
|
||||
if let Some(selected_file) = version.files.iter().find(|x| x.filename == file) {
|
||||
return Some(selected_file);
|
||||
}
|
||||
|
||||
@@ -193,11 +188,7 @@ pub async fn version_file(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let project_data =
|
||||
database::models::Project::get_full_from_slug_or_project_id(
|
||||
&project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||
|
||||
let project = if let Some(data) = project_data {
|
||||
data
|
||||
@@ -229,11 +220,9 @@ pub async fn version_file(
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
};
|
||||
|
||||
let version = if let Some(version) = database::models::Version::get_full(
|
||||
database::models::ids::VersionId(vid.id),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
let version = if let Some(version) =
|
||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||
.await?
|
||||
{
|
||||
version
|
||||
} else {
|
||||
@@ -263,9 +252,7 @@ pub async fn version_file(
|
||||
return Ok(HttpResponse::Ok()
|
||||
.content_type("text/xml")
|
||||
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
|
||||
} else if let Some(selected_file) =
|
||||
find_file(&project_id, &project, &version, &file)
|
||||
{
|
||||
} else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) {
|
||||
return Ok(HttpResponse::TemporaryRedirect()
|
||||
.append_header(("location", &*selected_file.url))
|
||||
.body(""));
|
||||
@@ -282,11 +269,7 @@ pub async fn version_file_sha1(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let project_data =
|
||||
database::models::Project::get_full_from_slug_or_project_id(
|
||||
&project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||
|
||||
let project = if let Some(data) = project_data {
|
||||
data
|
||||
@@ -318,11 +301,9 @@ pub async fn version_file_sha1(
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
};
|
||||
|
||||
let version = if let Some(version) = database::models::Version::get_full(
|
||||
database::models::ids::VersionId(vid.id),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
let version = if let Some(version) =
|
||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||
.await?
|
||||
{
|
||||
version
|
||||
} else {
|
||||
@@ -343,11 +324,7 @@ pub async fn version_file_sha512(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let project_data =
|
||||
database::models::Project::get_full_from_slug_or_project_id(
|
||||
&project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||
|
||||
let project = if let Some(data) = project_data {
|
||||
data
|
||||
@@ -379,11 +356,9 @@ pub async fn version_file_sha512(
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
};
|
||||
|
||||
let version = if let Some(version) = database::models::Version::get_full(
|
||||
database::models::ids::VersionId(vid.id),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
let version = if let Some(version) =
|
||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||
.await?
|
||||
{
|
||||
version
|
||||
} else {
|
||||
|
||||
@@ -97,30 +97,28 @@ impl actix_web::ResponseError for ApiError {
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::build(self.status_code()).json(
|
||||
crate::models::error::ApiError {
|
||||
error: match self {
|
||||
ApiError::Env(..) => "environment_error",
|
||||
ApiError::SqlxDatabase(..) => "database_error",
|
||||
ApiError::Database(..) => "database_error",
|
||||
ApiError::Authentication(..) => "unauthorized",
|
||||
ApiError::CustomAuthentication(..) => "unauthorized",
|
||||
ApiError::Xml(..) => "xml_error",
|
||||
ApiError::Json(..) => "json_error",
|
||||
ApiError::Search(..) => "search_error",
|
||||
ApiError::Indexing(..) => "indexing_error",
|
||||
ApiError::FileHosting(..) => "file_hosting_error",
|
||||
ApiError::InvalidInput(..) => "invalid_input",
|
||||
ApiError::Validation(..) => "invalid_input",
|
||||
ApiError::Analytics(..) => "analytics_error",
|
||||
ApiError::Crypto(..) => "crypto_error",
|
||||
ApiError::Payments(..) => "payments_error",
|
||||
ApiError::DiscordError(..) => "discord_error",
|
||||
ApiError::Decoding(..) => "decoding_error",
|
||||
ApiError::ImageError(..) => "invalid_image",
|
||||
},
|
||||
description: &self.to_string(),
|
||||
HttpResponse::build(self.status_code()).json(crate::models::error::ApiError {
|
||||
error: match self {
|
||||
ApiError::Env(..) => "environment_error",
|
||||
ApiError::SqlxDatabase(..) => "database_error",
|
||||
ApiError::Database(..) => "database_error",
|
||||
ApiError::Authentication(..) => "unauthorized",
|
||||
ApiError::CustomAuthentication(..) => "unauthorized",
|
||||
ApiError::Xml(..) => "xml_error",
|
||||
ApiError::Json(..) => "json_error",
|
||||
ApiError::Search(..) => "search_error",
|
||||
ApiError::Indexing(..) => "indexing_error",
|
||||
ApiError::FileHosting(..) => "file_hosting_error",
|
||||
ApiError::InvalidInput(..) => "invalid_input",
|
||||
ApiError::Validation(..) => "invalid_input",
|
||||
ApiError::Analytics(..) => "analytics_error",
|
||||
ApiError::Crypto(..) => "crypto_error",
|
||||
ApiError::Payments(..) => "payments_error",
|
||||
ApiError::DiscordError(..) => "discord_error",
|
||||
ApiError::Decoding(..) => "decoding_error",
|
||||
ApiError::ImageError(..) => "invalid_image",
|
||||
},
|
||||
)
|
||||
description: &self.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ use sqlx::PgPool;
|
||||
|
||||
use crate::database;
|
||||
use crate::models::projects::VersionType;
|
||||
use crate::util::auth::{
|
||||
filter_authorized_versions, get_user_from_headers, is_authorized,
|
||||
};
|
||||
use crate::util::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
||||
|
||||
use super::ApiError;
|
||||
|
||||
@@ -26,10 +24,9 @@ pub async fn forge_updates(
|
||||
|
||||
let (id,) = info.into_inner();
|
||||
|
||||
let project =
|
||||
database::models::Project::get_from_slug_or_project_id(&id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||
let project = database::models::Project::get_from_slug_or_project_id(&id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
@@ -48,11 +45,9 @@ pub async fn forge_updates(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let versions =
|
||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
let versions = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
|
||||
let mut versions =
|
||||
filter_authorized_versions(versions, &user_option, &pool).await?;
|
||||
let mut versions = filter_authorized_versions(versions, &user_option, &pool).await?;
|
||||
|
||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
||||
|
||||
|
||||
@@ -37,26 +37,22 @@ pub async fn count_download(
|
||||
download_body: web::Json<DownloadBody>,
|
||||
download_queue: web::Data<Arc<DownloadQueue>>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let project_id: crate::database::models::ids::ProjectId =
|
||||
download_body.project_id.into();
|
||||
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();
|
||||
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(
|
||||
&download_body.version_name,
|
||||
)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(&download_body.version_name)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
let (version_id, project_id, file_type) = if let Some(version) =
|
||||
sqlx::query!(
|
||||
"
|
||||
let (version_id, project_id, file_type) = if let Some(version) = sqlx::query!(
|
||||
"
|
||||
SELECT v.id id, v.mod_id mod_id, file_type FROM files f
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE f.url = $1
|
||||
",
|
||||
download_body.url,
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
download_body.url,
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.mod_id, version.file_type)
|
||||
} else if let Some(version) = sqlx::query!(
|
||||
@@ -143,17 +139,11 @@ pub async fn process_payout(
|
||||
)])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Analytics(
|
||||
"Error while fetching payout multipliers!".to_string(),
|
||||
)
|
||||
})?
|
||||
.map_err(|_| ApiError::Analytics("Error while fetching payout multipliers!".to_string()))?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Analytics(
|
||||
"Error while deserializing payout multipliers!".to_string(),
|
||||
)
|
||||
ApiError::Analytics("Error while deserializing payout multipliers!".to_string())
|
||||
})?;
|
||||
|
||||
struct Project {
|
||||
@@ -176,26 +166,33 @@ pub async fn process_payout(
|
||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||
WHERE m.id = ANY($1) AND m.monetization_status = $2
|
||||
",
|
||||
&multipliers.values.keys().flat_map(|x| x.parse::<i64>().ok()).collect::<Vec<i64>>(),
|
||||
&multipliers
|
||||
.values
|
||||
.keys()
|
||||
.flat_map(|x| x.parse::<i64>().ok())
|
||||
.collect::<Vec<i64>>(),
|
||||
MonetizationStatus::Monetized.as_str(),
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
if let Some(project) = projects_map.get_mut(&row.id) {
|
||||
project.team_members.push((row.user_id, row.payouts_split));
|
||||
} else {
|
||||
projects_map.insert(row.id, Project {
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
if let Some(project) = projects_map.get_mut(&row.id) {
|
||||
project.team_members.push((row.user_id, row.payouts_split));
|
||||
} else {
|
||||
projects_map.insert(
|
||||
row.id,
|
||||
Project {
|
||||
project_type: row.project_type,
|
||||
team_members: vec![(row.user_id, row.payouts_split)],
|
||||
split_team_members: Default::default()
|
||||
});
|
||||
}
|
||||
split_team_members: Default::default(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
futures::future::ready(Ok(()))
|
||||
})
|
||||
.await?;
|
||||
futures::future::ready(Ok(()))
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Specific Payout Conditions (ex: modpack payout split)
|
||||
let mut projects_split_dependencies = Vec::new();
|
||||
@@ -208,8 +205,7 @@ pub async fn process_payout(
|
||||
|
||||
if !projects_split_dependencies.is_empty() {
|
||||
// (dependent_id, (dependency_id, times_depended))
|
||||
let mut project_dependencies: HashMap<i64, Vec<(i64, i64)>> =
|
||||
HashMap::new();
|
||||
let mut project_dependencies: HashMap<i64, Vec<(i64, i64)>> = HashMap::new();
|
||||
// dependency_ids to fetch team members from
|
||||
let mut fetch_team_members: Vec<i64> = Vec::new();
|
||||
|
||||
@@ -229,14 +225,11 @@ pub async fn process_payout(
|
||||
if let Some(row) = e.right() {
|
||||
fetch_team_members.push(row.id);
|
||||
|
||||
if let Some(project) = project_dependencies.get_mut(&row.mod_id)
|
||||
{
|
||||
if let Some(project) = project_dependencies.get_mut(&row.mod_id) {
|
||||
project.push((row.id, row.times_depended.unwrap_or(0)));
|
||||
} else {
|
||||
project_dependencies.insert(
|
||||
row.mod_id,
|
||||
vec![(row.id, row.times_depended.unwrap_or(0))],
|
||||
);
|
||||
project_dependencies
|
||||
.insert(row.mod_id, vec![(row.id, row.times_depended.unwrap_or(0))]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +238,7 @@ pub async fn process_payout(
|
||||
.await?;
|
||||
|
||||
// (project_id, (user_id, payouts_split))
|
||||
let mut team_members: HashMap<i64, Vec<(i64, Decimal)>> =
|
||||
HashMap::new();
|
||||
let mut team_members: HashMap<i64, Vec<(i64, Decimal)>> = HashMap::new();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -263,8 +255,7 @@ pub async fn process_payout(
|
||||
if let Some(project) = team_members.get_mut(&row.id) {
|
||||
project.push((row.user_id, row.payouts_split));
|
||||
} else {
|
||||
team_members
|
||||
.insert(row.id, vec![(row.user_id, row.payouts_split)]);
|
||||
team_members.insert(row.id, vec![(row.user_id, row.payouts_split)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,17 +272,14 @@ pub async fn process_payout(
|
||||
if dep_sum > 0 {
|
||||
for dependency in dependencies {
|
||||
let project_multiplier: Decimal =
|
||||
Decimal::from(dependency.1)
|
||||
/ Decimal::from(dep_sum);
|
||||
Decimal::from(dependency.1) / Decimal::from(dep_sum);
|
||||
|
||||
if let Some(members) = team_members.get(&dependency.0) {
|
||||
let members_sum: Decimal =
|
||||
members.iter().map(|x| x.1).sum();
|
||||
let members_sum: Decimal = members.iter().map(|x| x.1).sum();
|
||||
|
||||
if members_sum > Decimal::ZERO {
|
||||
for member in members {
|
||||
let member_multiplier: Decimal =
|
||||
member.1 / members_sum;
|
||||
let member_multiplier: Decimal = member.1 / members_sum;
|
||||
project.split_team_members.push((
|
||||
member.0,
|
||||
member_multiplier * project_multiplier,
|
||||
@@ -315,10 +303,8 @@ pub async fn process_payout(
|
||||
let split_given = Decimal::ONE / Decimal::from(5);
|
||||
let split_retention = Decimal::from(4) / Decimal::from(5);
|
||||
|
||||
let sum_splits: Decimal =
|
||||
project.team_members.iter().map(|x| x.1).sum();
|
||||
let sum_tm_splits: Decimal =
|
||||
project.split_team_members.iter().map(|x| x.1).sum();
|
||||
let sum_splits: Decimal = project.team_members.iter().map(|x| x.1).sum();
|
||||
let sum_tm_splits: Decimal = project.split_team_members.iter().map(|x| x.1).sum();
|
||||
|
||||
if sum_splits > Decimal::ZERO {
|
||||
for (user_id, split) in project.team_members {
|
||||
@@ -342,8 +328,8 @@ pub async fn process_payout(
|
||||
payout,
|
||||
start
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -378,8 +364,8 @@ pub async fn process_payout(
|
||||
payout,
|
||||
start
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
|
||||
@@ -58,12 +58,8 @@ impl actix_web::ResponseError for AuthorizationError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AuthorizationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AuthorizationError::SqlxDatabase(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
AuthorizationError::Database(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
AuthorizationError::SqlxDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AuthorizationError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AuthorizationError::SerDe(..) => StatusCode::BAD_REQUEST,
|
||||
AuthorizationError::Github(..) => StatusCode::FAILED_DEPENDENCY,
|
||||
AuthorizationError::InvalidCredentials => StatusCode::UNAUTHORIZED,
|
||||
@@ -84,9 +80,7 @@ impl actix_web::ResponseError for AuthorizationError {
|
||||
AuthorizationError::Github(..) => "github_error",
|
||||
AuthorizationError::InvalidCredentials => "invalid_credentials",
|
||||
AuthorizationError::Decoding(..) => "decoding_error",
|
||||
AuthorizationError::Authentication(..) => {
|
||||
"authentication_error"
|
||||
}
|
||||
AuthorizationError::Authentication(..) => "authentication_error",
|
||||
AuthorizationError::Url => "url_error",
|
||||
AuthorizationError::Banned => "user_banned",
|
||||
},
|
||||
@@ -119,16 +113,12 @@ pub async fn init(
|
||||
Query(info): Query<AuthorizationInit>,
|
||||
client: Data<PgPool>,
|
||||
) -> Result<HttpResponse, AuthorizationError> {
|
||||
let url =
|
||||
url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?;
|
||||
let url = url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?;
|
||||
|
||||
let allowed_callback_urls =
|
||||
parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||
let allowed_callback_urls = parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||
|
||||
let domain = url.domain().ok_or(AuthorizationError::Url)?;
|
||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x))
|
||||
&& domain != "modrinth.com"
|
||||
{
|
||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x)) && domain != "modrinth.com" {
|
||||
return Err(AuthorizationError::Url);
|
||||
}
|
||||
|
||||
@@ -215,8 +205,7 @@ pub async fn auth_callback(
|
||||
|
||||
let user = get_github_user_from_token(&token.access_token).await?;
|
||||
|
||||
let user_result =
|
||||
User::get_from_github_id(user.id, &mut *transaction).await?;
|
||||
let user_result = User::get_from_github_id(user.id, &mut *transaction).await?;
|
||||
match user_result {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
@@ -231,9 +220,7 @@ pub async fn auth_callback(
|
||||
return Err(AuthorizationError::Banned);
|
||||
}
|
||||
|
||||
let user_id =
|
||||
crate::database::models::generate_user_id(&mut transaction)
|
||||
.await?;
|
||||
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||
|
||||
let mut username_increment: i32 = 0;
|
||||
let mut username = None;
|
||||
|
||||
@@ -54,17 +54,11 @@ pub async fn init_checkout(
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Payments(
|
||||
"Error while creating checkout session!".to_string(),
|
||||
)
|
||||
})?
|
||||
.map_err(|_| ApiError::Payments("Error while creating checkout session!".to_string()))?
|
||||
.json::<Session>()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Payments(
|
||||
"Error while deserializing checkout response!".to_string(),
|
||||
)
|
||||
ApiError::Payments("Error while deserializing checkout response!".to_string())
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!(
|
||||
@@ -92,11 +86,7 @@ pub async fn init_customer_portal(
|
||||
.fetch_optional(&**pool)
|
||||
.await?
|
||||
.and_then(|x| x.stripe_customer_id)
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"User is not linked to stripe account!".to_string(),
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| ApiError::InvalidInput("User is not linked to stripe account!".to_string()))?;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
@@ -117,17 +107,11 @@ pub async fn init_customer_portal(
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Payments(
|
||||
"Error while creating billing session!".to_string(),
|
||||
)
|
||||
})?
|
||||
.map_err(|_| ApiError::Payments("Error while creating billing session!".to_string()))?
|
||||
.json::<Session>()
|
||||
.await
|
||||
.map_err(|_| {
|
||||
ApiError::Payments(
|
||||
"Error while deserializing billing response!".to_string(),
|
||||
)
|
||||
ApiError::Payments("Error while deserializing billing response!".to_string())
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!(
|
||||
@@ -166,27 +150,25 @@ pub async fn handle_stripe_webhook(
|
||||
if let Some(signature) = signature {
|
||||
type HmacSha256 = Hmac<sha2::Sha256>;
|
||||
|
||||
let mut key = HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes()).map_err(|_| {
|
||||
ApiError::Crypto(
|
||||
"Unable to initialize HMAC instance due to invalid key length!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let mut key =
|
||||
HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes())
|
||||
.map_err(|_| {
|
||||
ApiError::Crypto(
|
||||
"Unable to initialize HMAC instance due to invalid key length!"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
key.update(format!("{timestamp}.{body}").as_bytes());
|
||||
|
||||
key.verify(&signature).map_err(|_| {
|
||||
ApiError::Crypto(
|
||||
"Unable to verify webhook signature!".to_string(),
|
||||
)
|
||||
ApiError::Crypto("Unable to verify webhook signature!".to_string())
|
||||
})?;
|
||||
|
||||
if timestamp < (Utc::now() - Duration::minutes(5)).timestamp()
|
||||
|| timestamp
|
||||
> (Utc::now() + Duration::minutes(5)).timestamp()
|
||||
|| timestamp > (Utc::now() + Duration::minutes(5)).timestamp()
|
||||
{
|
||||
return Err(ApiError::Crypto(
|
||||
"Webhook signature expired!".to_string(),
|
||||
));
|
||||
return Err(ApiError::Crypto("Webhook signature expired!".to_string()));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::Crypto("Missing signature!".to_string()));
|
||||
@@ -256,8 +238,7 @@ pub async fn handle_stripe_webhook(
|
||||
// TODO: Currently hardcoded to midas-only. When we add more stuff should include price IDs
|
||||
match &*webhook.type_ {
|
||||
"checkout.session.completed" => {
|
||||
let session: CheckoutSession =
|
||||
serde_json::from_value(webhook.data.object)?;
|
||||
let session: CheckoutSession = serde_json::from_value(webhook.data.object)?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -276,8 +257,7 @@ pub async fn handle_stripe_webhook(
|
||||
|
||||
if let Some(item) = invoice.lines.data.first() {
|
||||
let expires: DateTime<Utc> = DateTime::from_utc(
|
||||
NaiveDateTime::from_timestamp_opt(item.period.end, 0)
|
||||
.unwrap_or_default(),
|
||||
NaiveDateTime::from_timestamp_opt(item.period.end, 0).unwrap_or_default(),
|
||||
Utc,
|
||||
) + Duration::days(1);
|
||||
|
||||
@@ -323,8 +303,7 @@ pub async fn handle_stripe_webhook(
|
||||
}
|
||||
}
|
||||
"customer.subscription.deleted" => {
|
||||
let session: Subscription =
|
||||
serde_json::from_value(webhook.data.object)?;
|
||||
let session: Subscription = serde_json::from_value(webhook.data.object)?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -334,8 +313,8 @@ pub async fn handle_stripe_webhook(
|
||||
",
|
||||
session.customer,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
@@ -46,18 +46,15 @@ pub async fn get_projects(
|
||||
count.count as i64
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| database::models::ProjectId(m.id)))
|
||||
})
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ProjectId(m.id))) })
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<_> =
|
||||
database::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::projects::Project::from)
|
||||
.collect();
|
||||
let projects: Vec<_> = database::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::projects::Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
|
||||
@@ -3,17 +3,19 @@ use crate::models::ids::NotificationId;
|
||||
use crate::models::notifications::Notification;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(notifications_get);
|
||||
cfg.service(notifications_delete);
|
||||
cfg.service(notifications_read);
|
||||
|
||||
cfg.service(
|
||||
web::scope("notification")
|
||||
.service(notification_get)
|
||||
.service(notifications_read)
|
||||
.service(notification_delete),
|
||||
);
|
||||
}
|
||||
@@ -31,7 +33,6 @@ pub async fn notifications_get(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
// TODO: this is really confusingly named.
|
||||
use database::models::notification_item::Notification as DBNotification;
|
||||
use database::models::NotificationId as DBNotificationId;
|
||||
|
||||
@@ -42,11 +43,8 @@ pub async fn notifications_get(
|
||||
.collect();
|
||||
|
||||
let notifications_data: Vec<DBNotification> =
|
||||
database::models::notification_item::Notification::get_many(
|
||||
¬ification_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let notifications: Vec<Notification> = notifications_data
|
||||
.into_iter()
|
||||
@@ -68,11 +66,7 @@ pub async fn notification_get(
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(
|
||||
id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if user.id == data.user_id.into() || user.role.is_admin() {
|
||||
@@ -85,6 +79,39 @@ pub async fn notification_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
pub async fn notification_read(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
database::models::notification_item::Notification::read(id.into(), &mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
"You are not authorized to read this notification!".to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
pub async fn notification_delete(
|
||||
req: HttpRequest,
|
||||
@@ -96,29 +123,21 @@ pub async fn notification_delete(
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(
|
||||
id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
database::models::notification_item::Notification::remove(
|
||||
id.into(),
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::remove(id.into(), &mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
"You are not authorized to delete this notification!"
|
||||
.to_string(),
|
||||
"You are not authorized to delete this notification!".to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
@@ -126,6 +145,41 @@ pub async fn notification_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[patch("notifications")]
|
||||
pub async fn notifications_read(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let notifications_data =
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||
|
||||
for notification in notifications_data {
|
||||
if notification.user_id == user.id.into() || user.role.is_admin() {
|
||||
notifications.push(notification.id);
|
||||
}
|
||||
}
|
||||
|
||||
database::models::notification_item::Notification::read_many(¬ifications, &mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[delete("notifications")]
|
||||
pub async fn notifications_delete(
|
||||
req: HttpRequest,
|
||||
@@ -134,23 +188,18 @@ pub async fn notifications_delete(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let notification_ids =
|
||||
serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let notifications_data =
|
||||
database::models::notification_item::Notification::get_many(
|
||||
¬ification_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> =
|
||||
Vec::new();
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||
|
||||
for notification in notifications_data {
|
||||
if notification.user_id == user.id.into() || user.role.is_admin() {
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::database::models::thread_item::ThreadBuilder;
|
||||
use crate::file_hosting::{FileHost, FileHostingError};
|
||||
use crate::models::error::ApiError;
|
||||
use crate::models::projects::{
|
||||
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus,
|
||||
SideType, VersionId, VersionStatus,
|
||||
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, SideType, VersionId,
|
||||
VersionStatus,
|
||||
};
|
||||
use crate::models::threads::ThreadType;
|
||||
use crate::models::users::UserId;
|
||||
@@ -79,14 +79,10 @@ impl actix_web::ResponseError for CreateError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::SqlxDatabaseError(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
CreateError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::IndexingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::FileHostingError(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
CreateError::FileHostingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
||||
@@ -97,9 +93,7 @@ impl actix_web::ResponseError for CreateError {
|
||||
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
||||
CreateError::CustomAuthenticationError(..) => {
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
CreateError::CustomAuthenticationError(..) => StatusCode::UNAUTHORIZED,
|
||||
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
||||
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST,
|
||||
@@ -347,21 +341,17 @@ async fn project_create_inner(
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
|
||||
// The currently logged in user
|
||||
let current_user =
|
||||
get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||
let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||
|
||||
let project_id: ProjectId =
|
||||
models::generate_project_id(transaction).await?.into();
|
||||
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
||||
|
||||
let project_create_data;
|
||||
let mut versions;
|
||||
let mut versions_map = std::collections::HashMap::new();
|
||||
let mut gallery_urls = Vec::new();
|
||||
|
||||
let all_game_versions =
|
||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_loaders =
|
||||
models::categories::Loader::list(&mut *transaction).await?;
|
||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||
|
||||
{
|
||||
// The first multipart field must be named "data" and contain a
|
||||
@@ -378,9 +368,9 @@ async fn project_create_inner(
|
||||
})?;
|
||||
|
||||
let content_disposition = field.content_disposition();
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError(String::from("Missing content name"))
|
||||
})?;
|
||||
let name = content_disposition
|
||||
.get_name()
|
||||
.ok_or_else(|| CreateError::MissingValueError(String::from("Missing content name")))?;
|
||||
|
||||
if name != "data" {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
@@ -390,22 +380,19 @@ async fn project_create_inner(
|
||||
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
data.extend_from_slice(
|
||||
&chunk.map_err(CreateError::MultipartError)?,
|
||||
);
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
}
|
||||
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
||||
|
||||
create_data.validate().map_err(|err| {
|
||||
CreateError::InvalidInput(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
create_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let slug_project_id_option: Option<ProjectId> =
|
||||
serde_json::from_str(&format!("\"{}\"", create_data.slug)).ok();
|
||||
|
||||
if let Some(slug_project_id) = slug_project_id_option {
|
||||
let slug_project_id: models::ids::ProjectId =
|
||||
slug_project_id.into();
|
||||
let slug_project_id: models::ids::ProjectId = slug_project_id.into();
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
||||
@@ -492,9 +479,7 @@ async fn project_create_inner(
|
||||
let content_disposition = field.content_disposition().clone();
|
||||
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError(
|
||||
"Missing content name".to_string(),
|
||||
)
|
||||
CreateError::MissingValueError("Missing content name".to_string())
|
||||
})?;
|
||||
|
||||
let (file_name, file_extension) =
|
||||
@@ -528,9 +513,7 @@ async fn project_create_inner(
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(item) =
|
||||
gallery_items.iter().find(|x| x.item == name)
|
||||
{
|
||||
if let Some(item) = gallery_items.iter().find(|x| x.item == name) {
|
||||
let data = read_from_field(
|
||||
&mut field,
|
||||
5 * (1 << 20),
|
||||
@@ -540,22 +523,13 @@ async fn project_create_inner(
|
||||
|
||||
let hash = sha1::Sha1::from(&data).hexdigest();
|
||||
let (_, file_extension) =
|
||||
super::version_creation::get_name_ext(
|
||||
&content_disposition,
|
||||
)?;
|
||||
let content_type =
|
||||
crate::util::ext::get_image_content_type(
|
||||
file_extension,
|
||||
)
|
||||
super::version_creation::get_name_ext(&content_disposition)?;
|
||||
let content_type = crate::util::ext::get_image_content_type(file_extension)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidIconFormat(
|
||||
file_extension.to_string(),
|
||||
)
|
||||
CreateError::InvalidIconFormat(file_extension.to_string())
|
||||
})?;
|
||||
|
||||
let url = format!(
|
||||
"data/{project_id}/images/{hash}.{file_extension}"
|
||||
);
|
||||
let url = format!("data/{project_id}/images/{hash}.{file_extension}");
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &url, data.freeze())
|
||||
.await?;
|
||||
@@ -588,8 +562,7 @@ async fn project_create_inner(
|
||||
|
||||
// `index` is always valid for these lists
|
||||
let created_version = versions.get_mut(index).unwrap();
|
||||
let version_data =
|
||||
project_create_data.initial_versions.get(index).unwrap();
|
||||
let version_data = project_create_data.initial_versions.get(index).unwrap();
|
||||
|
||||
// Upload the new jar file
|
||||
super::version_creation::upload_file(
|
||||
@@ -642,8 +615,7 @@ async fn project_create_inner(
|
||||
}
|
||||
|
||||
// Convert the list of category names to actual categories
|
||||
let mut categories =
|
||||
Vec::with_capacity(project_create_data.categories.len());
|
||||
let mut categories = Vec::with_capacity(project_create_data.categories.len());
|
||||
for category in &project_create_data.categories {
|
||||
let id = models::categories::Category::get_id_project(
|
||||
category,
|
||||
@@ -706,9 +678,7 @@ async fn project_create_inner(
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(
|
||||
"Client side type specified does not exist.".to_string(),
|
||||
)
|
||||
CreateError::InvalidInput("Client side type specified does not exist.".to_string())
|
||||
})?;
|
||||
|
||||
let server_side_id = models::categories::SideType::get_id(
|
||||
@@ -717,35 +687,27 @@ async fn project_create_inner(
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(
|
||||
"Server side type specified does not exist.".to_string(),
|
||||
)
|
||||
CreateError::InvalidInput("Server side type specified does not exist.".to_string())
|
||||
})?;
|
||||
|
||||
let license_id = spdx::Expression::parse(
|
||||
&project_create_data.license_id,
|
||||
)
|
||||
.map_err(|err| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Invalid SPDX license identifier: {err}"
|
||||
))
|
||||
})?;
|
||||
let license_id =
|
||||
spdx::Expression::parse(&project_create_data.license_id).map_err(|err| {
|
||||
CreateError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||
})?;
|
||||
|
||||
let mut donation_urls = vec![];
|
||||
|
||||
if let Some(urls) = &project_create_data.donation_urls {
|
||||
for url in urls {
|
||||
let platform_id = models::categories::DonationPlatform::get_id(
|
||||
&url.id,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Donation platform {} does not exist.",
|
||||
url.id.clone()
|
||||
))
|
||||
})?;
|
||||
let platform_id =
|
||||
models::categories::DonationPlatform::get_id(&url.id, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Donation platform {} does not exist.",
|
||||
url.id.clone()
|
||||
))
|
||||
})?;
|
||||
|
||||
donation_urls.push(models::project_item::DonationUrl {
|
||||
platform_id,
|
||||
@@ -856,16 +818,10 @@ async fn project_create_inner(
|
||||
let _project_id = project_builder.insert(&mut *transaction).await?;
|
||||
|
||||
if status == ProjectStatus::Processing {
|
||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK")
|
||||
{
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
response.id,
|
||||
pool,
|
||||
webhook_url,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(response.id, pool, webhook_url, None)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,13 +844,12 @@ async fn create_initial_version(
|
||||
)));
|
||||
}
|
||||
|
||||
version_data.validate().map_err(|err| {
|
||||
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
version_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
|
||||
// Randomly generate a new id to be used for the version
|
||||
let version_id: VersionId =
|
||||
models::generate_version_id(transaction).await?.into();
|
||||
let version_id: VersionId = models::generate_version_id(transaction).await?.into();
|
||||
|
||||
let game_versions = version_data
|
||||
.game_versions
|
||||
@@ -963,15 +918,8 @@ async fn process_icon_upload(
|
||||
mut field: Field,
|
||||
cdn_url: &str,
|
||||
) -> Result<(String, Option<u32>), CreateError> {
|
||||
if let Some(content_type) =
|
||||
crate::util::ext::get_image_content_type(file_extension)
|
||||
{
|
||||
let data = read_from_field(
|
||||
&mut field,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
|
||||
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
|
||||
let color = crate::util::img::get_color_from_img(&data)?;
|
||||
|
||||
|
||||
@@ -6,16 +6,13 @@ use crate::models;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::projects::{
|
||||
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus,
|
||||
SearchRequest, SideType,
|
||||
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::models::threads::MessageBody;
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use crate::util::auth::{
|
||||
filter_authorized_projects, get_user_from_headers, is_authorized,
|
||||
};
|
||||
use crate::util::auth::{filter_authorized_projects, get_user_from_headers, is_authorized};
|
||||
use crate::util::routes::read_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
@@ -78,30 +75,30 @@ pub async fn random_projects_get(
|
||||
web::Query(count): web::Query<RandomProjects>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
count.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
count
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let project_ids = sqlx::query!(
|
||||
"
|
||||
"
|
||||
SELECT id FROM mods TABLESAMPLE SYSTEM_ROWS($1) WHERE status = ANY($2)
|
||||
",
|
||||
count.count as i32,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_searchable()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| database::models::ids::ProjectId(m.id)))
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
count.count as i32,
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_searchable())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) })
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let projects_data =
|
||||
database::models::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect::<Vec<_>>();
|
||||
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects_data))
|
||||
}
|
||||
@@ -123,13 +120,11 @@ pub async fn projects_get(
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let projects_data =
|
||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let projects =
|
||||
filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
||||
let projects = filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
@@ -143,10 +138,7 @@ pub async fn project_get(
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_data =
|
||||
database::models::Project::get_full_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
@@ -229,10 +221,7 @@ pub async fn dependency_list(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
@@ -259,12 +248,13 @@ pub async fn dependency_list(
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|x| {
|
||||
(
|
||||
x.dependency_id
|
||||
.map(database::models::VersionId),
|
||||
if x.mod_id == Some(0) { None } else { x.mod_id
|
||||
.map(database::models::ProjectId) },
|
||||
x.mod_dependency_id
|
||||
.map(database::models::ProjectId),
|
||||
x.dependency_id.map(database::models::VersionId),
|
||||
if x.mod_id == Some(0) {
|
||||
None
|
||||
} else {
|
||||
x.mod_id.map(database::models::ProjectId)
|
||||
},
|
||||
x.mod_dependency_id.map(database::models::ProjectId),
|
||||
)
|
||||
}))
|
||||
})
|
||||
@@ -430,15 +420,13 @@ pub async fn project_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_project.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
new_project
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result = database::models::Project::get_full_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let result =
|
||||
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?;
|
||||
|
||||
if let Some(project_item) = result {
|
||||
let id = project_item.inner.id;
|
||||
@@ -456,8 +444,7 @@ pub async fn project_edit(
|
||||
} else if let Some(ref member) = team_member {
|
||||
permissions = Some(member.permissions)
|
||||
} else if user.role.is_mod() {
|
||||
permissions =
|
||||
Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||
permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||
} else {
|
||||
permissions = None
|
||||
}
|
||||
@@ -518,12 +505,10 @@ pub async fn project_edit(
|
||||
if !(user.role.is_mod()
|
||||
|| !project_item.inner.status.is_approved()
|
||||
&& status == &ProjectStatus::Processing
|
||||
|| project_item.inner.status.is_approved()
|
||||
&& status.can_be_requested())
|
||||
|| project_item.inner.status.is_approved() && status.can_be_requested())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set this status!"
|
||||
.to_string(),
|
||||
"You don't have permission to set this status!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -545,9 +530,7 @@ pub async fn project_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if let Ok(webhook_url) =
|
||||
dotenvy::var("MODERATION_DISCORD_WEBHOOK")
|
||||
{
|
||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
@@ -559,9 +542,7 @@ pub async fn project_edit(
|
||||
}
|
||||
}
|
||||
|
||||
if status.is_approved()
|
||||
&& !project_item.inner.status.is_approved()
|
||||
{
|
||||
if status.is_approved() && !project_item.inner.status.is_approved() {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -575,9 +556,7 @@ pub async fn project_edit(
|
||||
}
|
||||
|
||||
if status.is_searchable() && !project_item.inner.webhook_sent {
|
||||
if let Ok(webhook_url) =
|
||||
dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
||||
{
|
||||
if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
@@ -607,8 +586,7 @@ pub async fn project_edit(
|
||||
FROM team_members tm
|
||||
WHERE tm.team_id = $1 AND tm.accepted
|
||||
",
|
||||
project_item.inner.team_id
|
||||
as database::models::ids::TeamId
|
||||
project_item.inner.team_id as database::models::ids::TeamId
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async {
|
||||
@@ -653,9 +631,7 @@ pub async fn project_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if project_item.inner.status.is_searchable()
|
||||
&& !status.is_searchable()
|
||||
{
|
||||
if project_item.inner.status.is_searchable() && !status.is_searchable() {
|
||||
delete_from_index(id.into(), config).await?;
|
||||
}
|
||||
}
|
||||
@@ -726,17 +702,14 @@ pub async fn project_edit(
|
||||
|
||||
for category in categories {
|
||||
let category_id =
|
||||
database::models::categories::Category::get_id(
|
||||
category,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?;
|
||||
database::models::categories::Category::get_id(category, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -761,17 +734,14 @@ pub async fn project_edit(
|
||||
|
||||
for category in categories {
|
||||
let category_id =
|
||||
database::models::categories::Category::get_id(
|
||||
category,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?;
|
||||
database::models::categories::Category::get_id(category, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -899,8 +869,7 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
let slug_project_id_option: Option<u64> =
|
||||
parse_base62(slug).ok();
|
||||
let slug_project_id_option: Option<u64> = parse_base62(slug).ok();
|
||||
if let Some(slug_project_id) = slug_project_id_option {
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
@@ -913,8 +882,7 @@ pub async fn project_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Slug collides with other project's id!"
|
||||
.to_string(),
|
||||
"Slug collides with other project's id!".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -933,8 +901,7 @@ pub async fn project_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Slug collides with other project's id!"
|
||||
.to_string(),
|
||||
"Slug collides with other project's id!".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -960,13 +927,12 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
let side_type_id =
|
||||
database::models::categories::SideType::get_id(
|
||||
new_side.as_str(),
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.expect("No database entry found for side type");
|
||||
let side_type_id = database::models::categories::SideType::get_id(
|
||||
new_side.as_str(),
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.expect("No database entry found for side type");
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -989,13 +955,12 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
let side_type_id =
|
||||
database::models::categories::SideType::get_id(
|
||||
new_side.as_str(),
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.expect("No database entry found for side type");
|
||||
let side_type_id = database::models::categories::SideType::get_id(
|
||||
new_side.as_str(),
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.expect("No database entry found for side type");
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -1025,9 +990,7 @@ pub async fn project_edit(
|
||||
}
|
||||
|
||||
spdx::Expression::parse(&license).map_err(|err| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Invalid SPDX license identifier: {err}"
|
||||
))
|
||||
ApiError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -1062,18 +1025,17 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
|
||||
for donation in donations {
|
||||
let platform_id =
|
||||
database::models::categories::DonationPlatform::get_id(
|
||||
&donation.id,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Platform {} does not exist.",
|
||||
donation.id.clone()
|
||||
))
|
||||
})?;
|
||||
let platform_id = database::models::categories::DonationPlatform::get_id(
|
||||
&donation.id,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Platform {} does not exist.",
|
||||
donation.id.clone()
|
||||
))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -1090,9 +1052,7 @@ pub async fn project_edit(
|
||||
}
|
||||
|
||||
if let Some(moderation_message) = &new_project.moderation_message {
|
||||
if !user.role.is_mod()
|
||||
&& project_item.inner.status != ProjectStatus::Approved
|
||||
{
|
||||
if !user.role.is_mod() && project_item.inner.status != ProjectStatus::Approved {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the moderation message of this project!"
|
||||
.to_string(),
|
||||
@@ -1112,12 +1072,8 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(moderation_message_body) =
|
||||
&new_project.moderation_message_body
|
||||
{
|
||||
if !user.role.is_mod()
|
||||
&& project_item.inner.status != ProjectStatus::Approved
|
||||
{
|
||||
if let Some(moderation_message_body) = &new_project.moderation_message_body {
|
||||
if !user.role.is_mod() && project_item.inner.status != ProjectStatus::Approved {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the moderation message body of this project!"
|
||||
.to_string(),
|
||||
@@ -1158,8 +1114,7 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(monetization_status) = &new_project.monetization_status
|
||||
{
|
||||
if let Some(monetization_status) = &new_project.monetization_status {
|
||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the monetization status of this project!"
|
||||
@@ -1167,8 +1122,7 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
if (*monetization_status
|
||||
== MonetizationStatus::ForceDemonetized
|
||||
if (*monetization_status == MonetizationStatus::ForceDemonetized
|
||||
|| project_item.inner.monetization_status
|
||||
== MonetizationStatus::ForceDemonetized)
|
||||
&& !user.role.is_mod()
|
||||
@@ -1276,9 +1230,9 @@ pub async fn projects_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
bulk_edit_project.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
bulk_edit_project
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let project_ids: Vec<database::models::ids::ProjectId> =
|
||||
serde_json::from_str::<Vec<ProjectId>>(&ids.ids)?
|
||||
@@ -1286,8 +1240,7 @@ pub async fn projects_edit(
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let projects_data =
|
||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
|
||||
if let Some(id) = project_ids
|
||||
.iter()
|
||||
@@ -1303,15 +1256,11 @@ pub async fn projects_edit(
|
||||
.iter()
|
||||
.map(|x| x.inner.team_id)
|
||||
.collect::<Vec<database::models::TeamId>>();
|
||||
let team_members = database::models::TeamMember::get_from_team_full_many(
|
||||
&team_ids, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let team_members =
|
||||
database::models::TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
||||
|
||||
let categories =
|
||||
database::models::categories::Category::list(&**pool).await?;
|
||||
let donation_platforms =
|
||||
database::models::categories::DonationPlatform::list(&**pool).await?;
|
||||
let categories = database::models::categories::Category::list(&**pool).await?;
|
||||
let donation_platforms = database::models::categories::DonationPlatform::list(&**pool).await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@@ -1322,9 +1271,10 @@ pub async fn projects_edit(
|
||||
.find(|x| x.team_id == project.inner.team_id)
|
||||
{
|
||||
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
format!("You do not have the permissions to bulk edit project {}!", project.inner.title),
|
||||
));
|
||||
return Err(ApiError::CustomAuthentication(format!(
|
||||
"You do not have the permissions to bulk edit project {}!",
|
||||
project.inner.title
|
||||
)));
|
||||
}
|
||||
} else if project.inner.status.is_hidden() {
|
||||
return Ok(HttpResponse::NotFound().body(""));
|
||||
@@ -1336,18 +1286,15 @@ pub async fn projects_edit(
|
||||
};
|
||||
}
|
||||
|
||||
let mut set_categories =
|
||||
if let Some(categories) = bulk_edit_project.categories.clone() {
|
||||
categories
|
||||
} else {
|
||||
project.categories.clone()
|
||||
};
|
||||
let mut set_categories = if let Some(categories) = bulk_edit_project.categories.clone() {
|
||||
categories
|
||||
} else {
|
||||
project.categories.clone()
|
||||
};
|
||||
|
||||
if let Some(delete_categories) = &bulk_edit_project.remove_categories {
|
||||
for category in delete_categories {
|
||||
if let Some(pos) =
|
||||
set_categories.iter().position(|x| x == category)
|
||||
{
|
||||
if let Some(pos) = set_categories.iter().position(|x| x == category) {
|
||||
set_categories.remove(pos);
|
||||
}
|
||||
}
|
||||
@@ -1394,34 +1341,27 @@ pub async fn projects_edit(
|
||||
project.inner.id as database::models::ids::ProjectId,
|
||||
category_id as database::models::ids::CategoryId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut set_additional_categories = if let Some(categories) =
|
||||
bulk_edit_project.additional_categories.clone()
|
||||
{
|
||||
categories
|
||||
} else {
|
||||
project.additional_categories.clone()
|
||||
};
|
||||
let mut set_additional_categories =
|
||||
if let Some(categories) = bulk_edit_project.additional_categories.clone() {
|
||||
categories
|
||||
} else {
|
||||
project.additional_categories.clone()
|
||||
};
|
||||
|
||||
if let Some(delete_categories) =
|
||||
&bulk_edit_project.remove_additional_categories
|
||||
{
|
||||
if let Some(delete_categories) = &bulk_edit_project.remove_additional_categories {
|
||||
for category in delete_categories {
|
||||
if let Some(pos) =
|
||||
set_additional_categories.iter().position(|x| x == category)
|
||||
{
|
||||
if let Some(pos) = set_additional_categories.iter().position(|x| x == category) {
|
||||
set_additional_categories.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(add_categories) =
|
||||
&bulk_edit_project.add_additional_categories
|
||||
{
|
||||
if let Some(add_categories) = &bulk_edit_project.add_additional_categories {
|
||||
for category in add_categories {
|
||||
if set_additional_categories.len() < 256 {
|
||||
set_additional_categories.push(category.clone());
|
||||
@@ -1476,16 +1416,14 @@ pub async fn projects_edit(
|
||||
url: d.url,
|
||||
})
|
||||
.collect();
|
||||
let mut set_donation_links = if let Some(donation_links) =
|
||||
bulk_edit_project.donation_urls.clone()
|
||||
{
|
||||
donation_links
|
||||
} else {
|
||||
project_donations.clone()
|
||||
};
|
||||
let mut set_donation_links =
|
||||
if let Some(donation_links) = bulk_edit_project.donation_urls.clone() {
|
||||
donation_links
|
||||
} else {
|
||||
project_donations.clone()
|
||||
};
|
||||
|
||||
if let Some(delete_donations) = &bulk_edit_project.remove_donation_urls
|
||||
{
|
||||
if let Some(delete_donations) = &bulk_edit_project.remove_donation_urls {
|
||||
for donation in delete_donations {
|
||||
if let Some(pos) = set_donation_links
|
||||
.iter()
|
||||
@@ -1532,8 +1470,8 @@ pub async fn projects_edit(
|
||||
platform_id as database::models::ids::DonationPlatformId,
|
||||
donation.url
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1616,8 +1554,7 @@ pub async fn project_schedule(
|
||||
|
||||
if scheduling_data.time < Utc::now() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You cannot schedule a project to be released in the past!"
|
||||
.to_string(),
|
||||
"You cannot schedule a project to be released in the past!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1628,10 +1565,7 @@ pub async fn project_schedule(
|
||||
}
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
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(
|
||||
@@ -1684,22 +1618,15 @@ pub async fn project_icon_edit(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) =
|
||||
crate::util::ext::get_image_content_type(&ext.ext)
|
||||
{
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item =
|
||||
database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
@@ -1711,15 +1638,12 @@ pub async fn project_icon_edit(
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's icon."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1732,12 +1656,8 @@ pub async fn project_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
|
||||
let color = crate::util::img::get_color_from_img(&bytes)?;
|
||||
|
||||
@@ -1787,15 +1707,11 @@ pub async fn delete_project_icon(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -1806,15 +1722,12 @@ pub async fn delete_project_icon(
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's icon."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1866,32 +1779,24 @@ pub async fn add_gallery_item(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) =
|
||||
crate::util::ext::get_image_content_type(&ext.ext)
|
||||
{
|
||||
item.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||
item.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item =
|
||||
database::models::Project::get_full_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if project_item.gallery_items.len() > 64 {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You have reached the maximum of gallery images to upload."
|
||||
.to_string(),
|
||||
"You have reached the maximum of gallery images to upload.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1904,15 +1809,12 @@ pub async fn add_gallery_item(
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2013,19 +1915,14 @@ pub async fn edit_gallery_item(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
item.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
item.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -2036,15 +1933,12 @@ pub async fn edit_gallery_item(
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2157,15 +2051,11 @@ pub async fn delete_gallery_item(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -2176,15 +2066,12 @@ pub async fn delete_gallery_item(
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2241,30 +2128,23 @@ pub async fn project_delete(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let project = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_project(
|
||||
project.id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let team_member = database::models::TeamMember::get_from_user_id_project(
|
||||
project.id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member
|
||||
.permissions
|
||||
@@ -2278,9 +2158,7 @@ pub async fn project_delete(
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result =
|
||||
database::models::Project::remove_full(project.id, &mut transaction)
|
||||
.await?;
|
||||
let result = database::models::Project::remove_full(project.id, &mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -2302,15 +2180,11 @@ pub async fn project_follow(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
let user_id: database::models::ids::UserId = user.id.into();
|
||||
let project_id: database::models::ids::ProjectId = result.id;
|
||||
@@ -2375,15 +2249,11 @@ pub async fn project_unfollow(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
let user_id: database::models::ids::UserId = user.id.into();
|
||||
let project_id = result.id;
|
||||
@@ -2439,8 +2309,7 @@ pub async fn delete_from_index(
|
||||
id: ProjectId,
|
||||
config: web::Data<SearchConfig>,
|
||||
) -> Result<(), meilisearch_sdk::errors::Error> {
|
||||
let client =
|
||||
meilisearch_sdk::client::Client::new(&*config.address, &*config.key);
|
||||
let client = meilisearch_sdk::client::Client::new(&*config.address, &*config.key);
|
||||
|
||||
let indexes: IndexesResults = client.get_indexes().await?;
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
use crate::database::models::thread_item::{
|
||||
ThreadBuilder, ThreadMessageBuilder,
|
||||
};
|
||||
use crate::models::ids::{
|
||||
base62_impl::parse_base62, ProjectId, UserId, VersionId,
|
||||
};
|
||||
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
||||
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||
use crate::models::reports::{ItemType, Report};
|
||||
use crate::models::threads::{MessageBody, ThreadType};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::{
|
||||
check_is_moderator_from_headers, get_user_from_headers,
|
||||
};
|
||||
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::StreamExt;
|
||||
@@ -41,31 +35,24 @@ pub async fn report_create(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let current_user =
|
||||
get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||
let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = body.next().await {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInput(
|
||||
"Error while parsing request payload!".to_string(),
|
||||
)
|
||||
ApiError::InvalidInput("Error while parsing request payload!".to_string())
|
||||
})?);
|
||||
}
|
||||
let new_report: CreateReport = serde_json::from_slice(bytes.as_ref())?;
|
||||
|
||||
let id =
|
||||
crate::database::models::generate_report_id(&mut transaction).await?;
|
||||
let id = crate::database::models::generate_report_id(&mut transaction).await?;
|
||||
let report_type = crate::database::models::categories::ReportType::get_id(
|
||||
&new_report.report_type,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Invalid report type: {}",
|
||||
new_report.report_type
|
||||
))
|
||||
ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type))
|
||||
})?;
|
||||
|
||||
let thread_id = ThreadBuilder {
|
||||
@@ -92,8 +79,7 @@ pub async fn report_create(
|
||||
|
||||
match new_report.item_type {
|
||||
ItemType::Project => {
|
||||
let project_id =
|
||||
ProjectId(parse_base62(new_report.item_id.as_str())?);
|
||||
let project_id = ProjectId(parse_base62(new_report.item_id.as_str())?);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM mods WHERE id = $1)",
|
||||
@@ -112,8 +98,7 @@ pub async fn report_create(
|
||||
report.project_id = Some(project_id.into())
|
||||
}
|
||||
ItemType::Version => {
|
||||
let version_id =
|
||||
VersionId(parse_base62(new_report.item_id.as_str())?);
|
||||
let version_id = VersionId(parse_base62(new_report.item_id.as_str())?);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)",
|
||||
@@ -236,11 +221,8 @@ pub async fn reports(
|
||||
.await?
|
||||
};
|
||||
|
||||
let query_reports = crate::database::models::report_item::Report::get_many(
|
||||
&report_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let query_reports =
|
||||
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||
|
||||
let mut reports = Vec::new();
|
||||
|
||||
@@ -260,8 +242,7 @@ pub async fn report_get(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id = info.into_inner().0.into();
|
||||
|
||||
let report =
|
||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if !user.role.is_mod() && report.reporter != user.id.into() {
|
||||
@@ -291,8 +272,7 @@ pub async fn report_edit(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id = info.into_inner().0.into();
|
||||
|
||||
let report =
|
||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if !user.role.is_mod() && report.user_id != Some(user.id.into()) {
|
||||
@@ -380,9 +360,7 @@ pub async fn report_delete(
|
||||
}
|
||||
}
|
||||
|
||||
fn to_report(
|
||||
x: crate::database::models::report_item::QueryReport,
|
||||
) -> Result<Report, ApiError> {
|
||||
fn to_report(x: crate::database::models::report_item::QueryReport) -> Result<Report, ApiError> {
|
||||
let mut item_id = "".to_string();
|
||||
let mut item_type = ItemType::Unknown;
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
}
|
||||
|
||||
#[get("statistics")]
|
||||
pub async fn get_stats(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn get_stats(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let projects = sqlx::query!(
|
||||
"
|
||||
SELECT COUNT(id)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use super::ApiError;
|
||||
use crate::database::models;
|
||||
use crate::database::models::categories::{
|
||||
DonationPlatform, ProjectType, ReportType, SideType,
|
||||
};
|
||||
use crate::database::models::categories::{DonationPlatform, ProjectType, ReportType, SideType};
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use models::categories::{Category, GameVersion, Loader};
|
||||
@@ -34,9 +32,7 @@ pub struct CategoryData {
|
||||
// TODO: searching / filtering? Could be used to implement a live
|
||||
// searching category list
|
||||
#[get("category")]
|
||||
pub async fn category_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let results = Category::list(&**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -59,9 +55,7 @@ pub struct LoaderData {
|
||||
}
|
||||
|
||||
#[get("loader")]
|
||||
pub async fn loader_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn loader_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let mut results = Loader::list(&**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -97,11 +91,8 @@ pub async fn game_version_list(
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<GameVersionQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<GameVersionQueryData> = if query.type_.is_some()
|
||||
|| query.major.is_some()
|
||||
{
|
||||
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool)
|
||||
.await?
|
||||
let results: Vec<GameVersionQueryData> = if query.type_.is_some() || query.major.is_some() {
|
||||
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool).await?
|
||||
} else {
|
||||
GameVersion::list(&**pool).await?
|
||||
}
|
||||
@@ -145,9 +136,7 @@ pub struct LicenseText {
|
||||
}
|
||||
|
||||
#[get("license/{id}")]
|
||||
pub async fn license_text(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse, ApiError> {
|
||||
let license_id = params.into_inner().0;
|
||||
|
||||
if license_id == *crate::models::projects::DEFAULT_LICENSE_ID {
|
||||
@@ -176,41 +165,32 @@ pub struct DonationPlatformQueryData {
|
||||
}
|
||||
|
||||
#[get("donation_platform")]
|
||||
pub async fn donation_platform_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<DonationPlatformQueryData> =
|
||||
DonationPlatform::list(&**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| DonationPlatformQueryData {
|
||||
short: x.short,
|
||||
name: x.name,
|
||||
})
|
||||
.collect();
|
||||
pub async fn donation_platform_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<DonationPlatformQueryData> = DonationPlatform::list(&**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| DonationPlatformQueryData {
|
||||
short: x.short,
|
||||
name: x.name,
|
||||
})
|
||||
.collect();
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
#[get("report_type")]
|
||||
pub async fn report_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn report_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let results = ReportType::list(&**pool).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
#[get("project_type")]
|
||||
pub async fn project_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn project_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let results = ProjectType::list(&**pool).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
#[get("side_type")]
|
||||
pub async fn side_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn side_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
let results = SideType::list(&**pool).await?;
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
@@ -33,33 +33,23 @@ pub async fn team_members_get_project(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
let project_data =
|
||||
crate::database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
crate::database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||
|
||||
if let Some(project) = project_data {
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(project.team_id, &**pool).await?;
|
||||
let members_data = TeamMember::get_from_team_full(project.team_id, &**pool).await?;
|
||||
|
||||
let current_user =
|
||||
get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
if let Some(user) = current_user {
|
||||
let team_member = TeamMember::get_from_user_id(
|
||||
project.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
let team_member =
|
||||
TeamMember::get_from_user_id(project.team_id, user.id.into(), &**pool)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
if team_member.is_some() {
|
||||
let team_members: Vec<_> = members_data
|
||||
.into_iter()
|
||||
.map(|data| {
|
||||
crate::models::teams::TeamMember::from(data, false)
|
||||
})
|
||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
||||
.collect();
|
||||
|
||||
return Ok(HttpResponse::Ok().json(team_members));
|
||||
@@ -85,16 +75,14 @@ pub async fn team_members_get(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(id.into(), &**pool).await?;
|
||||
let members_data = TeamMember::get_from_team_full(id.into(), &**pool).await?;
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
if let Some(user) = ¤t_user {
|
||||
let team_member =
|
||||
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
if team_member.is_some() {
|
||||
let team_members: Vec<_> = members_data
|
||||
@@ -139,8 +127,7 @@ pub async fn teams_get(
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
||||
|
||||
let teams_data =
|
||||
TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
||||
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
let accepted = if let Some(user) = current_user {
|
||||
@@ -159,9 +146,8 @@ pub async fn teams_get(
|
||||
|
||||
for (id, member_data) in &teams_groups {
|
||||
if accepted.contains(&id) {
|
||||
let team_members = member_data.map(|data| {
|
||||
crate::models::teams::TeamMember::from(data, false)
|
||||
});
|
||||
let team_members =
|
||||
member_data.map(|data| crate::models::teams::TeamMember::from(data, false));
|
||||
|
||||
teams.push(team_members.collect());
|
||||
|
||||
@@ -187,12 +173,8 @@ pub async fn join_team(
|
||||
let team_id = info.into_inner().0.into();
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let member = TeamMember::get_from_user_id_pending(
|
||||
team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||
|
||||
if let Some(member) = member {
|
||||
if member.accepted {
|
||||
@@ -258,20 +240,17 @@ pub async fn add_team_member(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to invite users to this team"
|
||||
.to_string(),
|
||||
"You don't have permission to invite users to this team".to_string(),
|
||||
));
|
||||
}
|
||||
if !member.permissions.contains(new_member.permissions) {
|
||||
@@ -286,9 +265,7 @@ pub async fn add_team_member(
|
||||
));
|
||||
}
|
||||
|
||||
if new_member.payouts_split < Decimal::ZERO
|
||||
|| new_member.payouts_split > Decimal::from(5000)
|
||||
{
|
||||
if new_member.payouts_split < Decimal::ZERO || new_member.payouts_split > Decimal::from(5000) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Payouts split must be between 0 and 5000!".to_string(),
|
||||
));
|
||||
@@ -308,21 +285,16 @@ pub async fn add_team_member(
|
||||
));
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"There is already a pending member request for this user"
|
||||
.to_string(),
|
||||
"There is already a pending member request for this user".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
crate::database::models::User::get(member.user_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("An invalid User ID specified".to_string())
|
||||
})?;
|
||||
.ok_or_else(|| ApiError::InvalidInput("An invalid User ID specified".to_string()))?;
|
||||
|
||||
let new_id =
|
||||
crate::database::models::ids::generate_team_member_id(&mut transaction)
|
||||
.await?;
|
||||
let new_id = crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||
TeamMember {
|
||||
id: new_id,
|
||||
team_id,
|
||||
@@ -383,24 +355,20 @@ pub async fn edit_team_member(
|
||||
let user_id = ids.1.into();
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let edit_member_db =
|
||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
let edit_member_db = TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@@ -408,30 +376,26 @@ pub async fn edit_team_member(
|
||||
&& (edit_member.role.is_some() || edit_member.permissions.is_some())
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The owner's permission and role of a team cannot be edited"
|
||||
.to_string(),
|
||||
"The owner's permission and role of a team cannot be edited".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(new_permissions) = edit_member.permissions {
|
||||
if !member.permissions.contains(new_permissions) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The new permissions have permissions that you don't have"
|
||||
.to_string(),
|
||||
"The new permissions have permissions that you don't have".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(payouts_split) = edit_member.payouts_split {
|
||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000)
|
||||
{
|
||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Payouts split must be between 0 and 5000!".to_string(),
|
||||
));
|
||||
@@ -478,38 +442,26 @@ pub async fn transfer_ownership(
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
if !current_user.role.is_admin() {
|
||||
let member = TeamMember::get_from_user_id(
|
||||
id.into(),
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if member.role != crate::models::teams::OWNER_ROLE {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit the ownership of this team"
|
||||
.to_string(),
|
||||
"You don't have permission to edit the ownership of this team".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let new_member = TeamMember::get_from_user_id(
|
||||
id.into(),
|
||||
new_owner.user_id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The new owner specified does not exist".to_string(),
|
||||
)
|
||||
})?;
|
||||
let new_member = TeamMember::get_from_user_id(id.into(), new_owner.user_id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The new owner specified does not exist".to_string())
|
||||
})?;
|
||||
|
||||
if !new_member.accepted {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -559,18 +511,15 @@ pub async fn remove_team_member(
|
||||
let user_id = ids.1.into();
|
||||
|
||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let delete_member =
|
||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
||||
let delete_member = TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
||||
|
||||
if let Some(delete_member) = delete_member {
|
||||
if delete_member.role == crate::models::teams::OWNER_ROLE {
|
||||
@@ -586,8 +535,7 @@ pub async fn remove_team_member(
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if delete_member.user_id == member.user_id
|
||||
|| (member.permissions.contains(Permissions::REMOVE_MEMBER)
|
||||
&& member.accepted)
|
||||
|| (member.permissions.contains(Permissions::REMOVE_MEMBER) && member.accepted)
|
||||
{
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
@@ -596,8 +544,7 @@ pub async fn remove_team_member(
|
||||
));
|
||||
}
|
||||
} else if delete_member.user_id == member.user_id
|
||||
|| (member.permissions.contains(Permissions::MANAGE_INVITES)
|
||||
&& member.accepted)
|
||||
|| (member.permissions.contains(Permissions::MANAGE_INVITES) && member.accepted)
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
@@ -605,8 +552,7 @@ pub async fn remove_team_member(
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to cancel a team invite"
|
||||
.to_string(),
|
||||
"You do not have permission to cancel a team invite".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,10 @@ use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||
use crate::models::ids::{ReportId, ThreadMessageId};
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::projects::{ProjectId, ProjectStatus};
|
||||
use crate::models::threads::{
|
||||
MessageBody, Thread, ThreadId, ThreadMessage, ThreadType,
|
||||
};
|
||||
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadMessage, ThreadType};
|
||||
use crate::models::users::User;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::{
|
||||
check_is_moderator_from_headers, get_user_from_headers,
|
||||
};
|
||||
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use futures::TryStreamExt;
|
||||
use serde::Deserialize;
|
||||
@@ -42,13 +38,13 @@ pub async fn is_authorized_thread(
|
||||
Ok(match thread.type_ {
|
||||
ThreadType::Report => {
|
||||
let report_exists = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)",
|
||||
thread.id as database::models::ids::ThreadId,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
.exists;
|
||||
"SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)",
|
||||
thread.id as database::models::ids::ThreadId,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
.exists;
|
||||
|
||||
report_exists.unwrap_or(false)
|
||||
}
|
||||
@@ -80,8 +76,7 @@ pub async fn filter_authorized_threads(
|
||||
|
||||
for thread in threads {
|
||||
if user.role.is_mod()
|
||||
|| (thread.type_ == ThreadType::DirectMessage
|
||||
&& thread.members.contains(&user_id))
|
||||
|| (thread.type_ == ThreadType::DirectMessage && thread.members.contains(&user_id))
|
||||
{
|
||||
return_threads.push(thread);
|
||||
} else {
|
||||
@@ -106,23 +101,23 @@ pub async fn filter_authorized_threads(
|
||||
&*project_thread_ids,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_many(&***pool)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
check_threads.retain(|x| {
|
||||
let bool = Some(x.id.0) == row.thread_id;
|
||||
.fetch_many(&***pool)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
check_threads.retain(|x| {
|
||||
let bool = Some(x.id.0) == row.thread_id;
|
||||
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
|
||||
!bool
|
||||
});
|
||||
}
|
||||
!bool
|
||||
});
|
||||
}
|
||||
|
||||
futures::future::ready(Ok(()))
|
||||
})
|
||||
.await?;
|
||||
futures::future::ready(Ok(()))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
let report_thread_ids = check_threads
|
||||
@@ -176,12 +171,11 @@ pub async fn filter_authorized_threads(
|
||||
.collect::<Vec<database::models::UserId>>(),
|
||||
);
|
||||
|
||||
let users: Vec<User> =
|
||||
database::models::User::get_many(&user_ids, &***pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
let users: Vec<User> = database::models::User::get_many(&user_ids, &***pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
|
||||
let mut final_threads = Vec::new();
|
||||
|
||||
@@ -210,11 +204,7 @@ pub async fn filter_authorized_threads(
|
||||
Ok(final_threads)
|
||||
}
|
||||
|
||||
fn convert_thread(
|
||||
data: database::models::Thread,
|
||||
users: Vec<User>,
|
||||
user: &User,
|
||||
) -> Thread {
|
||||
fn convert_thread(data: database::models::Thread, users: Vec<User>, user: &User) -> Thread {
|
||||
let thread_type = data.type_;
|
||||
|
||||
Thread {
|
||||
@@ -279,16 +269,13 @@ pub async fn thread_get(
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let users: Vec<User> =
|
||||
database::models::User::get_many(authors, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
let users: Vec<User> = database::models::User::get_many(authors, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(convert_thread(data, users, &user))
|
||||
);
|
||||
return Ok(HttpResponse::Ok().json(convert_thread(data, users, &user)));
|
||||
}
|
||||
}
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
@@ -313,8 +300,7 @@ pub async fn threads_get(
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let threads_data =
|
||||
database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
||||
let threads_data = database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
||||
|
||||
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
||||
|
||||
@@ -356,17 +342,13 @@ pub async fn thread_send_message(
|
||||
}
|
||||
|
||||
if let Some(replying_to) = replying_to {
|
||||
let thread_message = database::models::ThreadMessage::get(
|
||||
(*replying_to).into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let thread_message =
|
||||
database::models::ThreadMessage::get((*replying_to).into(), &**pool).await?;
|
||||
|
||||
if let Some(thread_message) = thread_message {
|
||||
if thread_message.thread_id != string {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Message replied to is from another thread!"
|
||||
.to_string(),
|
||||
"Message replied to is from another thread!".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
@@ -394,17 +376,13 @@ pub async fn thread_send_message(
|
||||
None
|
||||
};
|
||||
|
||||
if report.as_ref().map(|x| x.closed).unwrap_or(false)
|
||||
&& !user.role.is_mod()
|
||||
{
|
||||
if report.as_ref().map(|x| x.closed).unwrap_or(false) && !user.role.is_mod() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You may not reply to a closed report".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let (mod_notif, (user_notif, team_id)) = if thread.type_
|
||||
== ThreadType::Project
|
||||
{
|
||||
let (mod_notif, (user_notif, team_id)) = if thread.type_ == ThreadType::Project {
|
||||
let record = sqlx::query!(
|
||||
"SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1",
|
||||
thread.id as database::models::ids::ThreadId,
|
||||
@@ -422,7 +400,17 @@ pub async fn thread_send_message(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(false, (thread.type_ == ThreadType::Report, None))
|
||||
(
|
||||
!user.role.is_mod(),
|
||||
(
|
||||
thread.type_ == ThreadType::Report
|
||||
&& !report
|
||||
.as_ref()
|
||||
.map(|x| x.reporter == user.id.into())
|
||||
.unwrap_or(false),
|
||||
None,
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -498,14 +486,11 @@ pub async fn moderation_inbox(
|
||||
"
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| database::models::ThreadId(m.id)))
|
||||
})
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ThreadId(m.id))) })
|
||||
.try_collect::<Vec<database::models::ThreadId>>()
|
||||
.await?;
|
||||
|
||||
let threads_data =
|
||||
database::models::Thread::get_many(&ids, &**pool).await?;
|
||||
let threads_data = database::models::Thread::get_many(&ids, &**pool).await?;
|
||||
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(threads))
|
||||
@@ -546,11 +531,7 @@ pub async fn message_delete(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let result = database::models::ThreadMessage::get(
|
||||
info.into_inner().0.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
||||
|
||||
if let Some(thread) = result {
|
||||
if !user.role.is_mod() && thread.author_id != Some(user.id.into()) {
|
||||
@@ -560,11 +541,7 @@ pub async fn message_delete(
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
database::models::ThreadMessage::remove_full(
|
||||
thread.id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
database::models::ThreadMessage::remove_full(thread.id, &mut transaction).await?;
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
@@ -2,9 +2,7 @@ use crate::database::models::User;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::notifications::Notification;
|
||||
use crate::models::projects::Project;
|
||||
use crate::models::users::{
|
||||
Badges, RecipientType, RecipientWallet, Role, UserId,
|
||||
};
|
||||
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
||||
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
@@ -24,6 +22,7 @@ use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(user_auth_get);
|
||||
cfg.service(user_data_get);
|
||||
cfg.service(users_get);
|
||||
|
||||
cfg.service(
|
||||
@@ -45,8 +44,44 @@ pub async fn user_auth_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
Ok(HttpResponse::Ok()
|
||||
.json(get_user_from_headers(req.headers(), &**pool).await?))
|
||||
Ok(HttpResponse::Ok().json(get_user_from_headers(req.headers(), &**pool).await?))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UserData {
|
||||
pub notifs_count: u64,
|
||||
pub followed_projects: Vec<crate::models::ids::ProjectId>,
|
||||
}
|
||||
|
||||
#[get("user_data")]
|
||||
pub async fn user_data_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let data = sqlx::query!(
|
||||
"
|
||||
SELECT COUNT(DISTINCT n.id) notifs_count, ARRAY_AGG(mf.mod_id) followed_projects FROM notifications n
|
||||
LEFT OUTER JOIN mod_follows mf ON mf.follower_id = $1
|
||||
WHERE user_id = $1 AND read = FALSE
|
||||
",
|
||||
user.id.0 as i64
|
||||
).fetch_optional(&**pool).await?;
|
||||
|
||||
if let Some(data) = data {
|
||||
Ok(HttpResponse::Ok().json(UserData {
|
||||
notifs_count: data.notifs_count.map(|x| x as u64).unwrap_or(0),
|
||||
followed_projects: data
|
||||
.followed_projects
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|x| crate::models::ids::ProjectId(x as u64))
|
||||
.collect(),
|
||||
}))
|
||||
} else {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -66,8 +101,7 @@ pub async fn users_get(
|
||||
|
||||
let users_data = User::get_many(&user_ids, &**pool).await?;
|
||||
|
||||
let users: Vec<crate::models::users::User> =
|
||||
users_data.into_iter().map(From::from).collect();
|
||||
let users: Vec<crate::models::users::User> = users_data.into_iter().map(From::from).collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
@@ -78,8 +112,7 @@ pub async fn user_get(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
let id_option: Option<UserId> =
|
||||
serde_json::from_str(&format!("\"{string}\"")).ok();
|
||||
let id_option: Option<UserId> = serde_json::from_str(&format!("\"{string}\"")).ok();
|
||||
|
||||
let mut user_data;
|
||||
|
||||
@@ -109,8 +142,7 @@ pub async fn projects_list(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
let user_id: UserId = id.into();
|
||||
@@ -121,13 +153,12 @@ pub async fn projects_list(
|
||||
|
||||
let project_data = User::get_projects(id, &**pool).await?;
|
||||
|
||||
let response: Vec<_> =
|
||||
crate::database::Project::get_many_full(&project_data, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| can_view_private || x.inner.status.is_searchable())
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
let response: Vec<_> = crate::database::Project::get_many_full(&project_data, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| can_view_private || x.inner.status.is_searchable())
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
@@ -192,12 +223,11 @@ pub async fn user_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_user.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
new_user
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
let user_id: UserId = id.into();
|
||||
@@ -320,23 +350,21 @@ pub async fn user_edit(
|
||||
|
||||
if let Some(payout_data) = &new_user.payout_data {
|
||||
if let Some(payout_data) = payout_data {
|
||||
if payout_data.payout_wallet_type
|
||||
== RecipientType::UserHandle
|
||||
if payout_data.payout_wallet_type == RecipientType::UserHandle
|
||||
&& payout_data.payout_wallet == RecipientWallet::Paypal
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You cannot use a paypal wallet with a user handle!"
|
||||
.to_string(),
|
||||
"You cannot use a paypal wallet with a user handle!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !match payout_data.payout_wallet_type {
|
||||
RecipientType::Email => validator::validate_email(
|
||||
&payout_data.payout_address,
|
||||
),
|
||||
RecipientType::Phone => validator::validate_phone(
|
||||
&payout_data.payout_address,
|
||||
),
|
||||
RecipientType::Email => {
|
||||
validator::validate_email(&payout_data.payout_address)
|
||||
}
|
||||
RecipientType::Phone => {
|
||||
validator::validate_phone(&payout_data.payout_address)
|
||||
}
|
||||
RecipientType::UserHandle => true,
|
||||
} {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -350,8 +378,8 @@ pub async fn user_edit(
|
||||
",
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?;
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if results.exists.unwrap_or(false) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -371,8 +399,8 @@ pub async fn user_edit(
|
||||
payout_data.payout_address,
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -382,8 +410,8 @@ pub async fn user_edit(
|
||||
",
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,20 +441,15 @@ pub async fn user_icon_edit(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) =
|
||||
crate::util::ext::get_image_content_type(&ext.ext)
|
||||
{
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool)
|
||||
.await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if user.id != id.into() && !user.role.is_mod() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this user's icon."
|
||||
.to_string(),
|
||||
"You don't have permission to edit this user's icon.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -452,12 +475,8 @@ pub async fn user_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
2097152,
|
||||
"Icons must be smaller than 2MiB",
|
||||
)
|
||||
.await?;
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 2097152, "Icons must be smaller than 2MiB").await?;
|
||||
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
let upload_data = file_host
|
||||
@@ -509,8 +528,7 @@ pub async fn user_delete(
|
||||
removal_type: web::Query<RemovalType>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if !user.role.is_admin() && user.id != id.into() {
|
||||
@@ -546,11 +564,7 @@ pub async fn user_follows(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||
&info.into_inner().0,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if !user.role.is_admin() && user.id != id.into() {
|
||||
@@ -576,12 +590,11 @@ pub async fn user_follows(
|
||||
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<_> =
|
||||
crate::database::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
let projects: Vec<_> = crate::database::Project::get_many_full(&project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
} else {
|
||||
@@ -596,11 +609,7 @@ pub async fn user_notifications(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||
&info.into_inner().0,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if !user.role.is_admin() && user.id != id.into() {
|
||||
@@ -638,14 +647,12 @@ pub async fn user_payouts(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if !user.role.is_admin() && user.id != id.into() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to see the payouts of this user!"
|
||||
.to_string(),
|
||||
"You do not have permission to see the payouts of this user!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -717,22 +724,18 @@ pub async fn user_payouts_request(
|
||||
let mut payouts_queue = payouts_queue.lock().await;
|
||||
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option =
|
||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||
|
||||
if let Some(id) = id_option {
|
||||
if !user.role.is_admin() && user.id != id.into() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to request payouts of this user!"
|
||||
.to_string(),
|
||||
"You do not have permission to request payouts of this user!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(payouts_data) = user.payout_data {
|
||||
if let Some(payout_address) = payouts_data.payout_address {
|
||||
if let Some(payout_wallet_type) =
|
||||
payouts_data.payout_wallet_type
|
||||
{
|
||||
if let Some(payout_wallet_type) = payouts_data.payout_wallet_type {
|
||||
if let Some(payout_wallet) = payouts_data.payout_wallet {
|
||||
return if data.amount < payouts_data.balance {
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -744,10 +747,15 @@ pub async fn user_payouts_request(
|
||||
value: data.amount,
|
||||
},
|
||||
receiver: payout_address,
|
||||
note: "Payment from Modrinth creator monetization program".to_string(),
|
||||
note: "Payment from Modrinth creator monetization program"
|
||||
.to_string(),
|
||||
recipient_type: payout_wallet_type.to_string().to_uppercase(),
|
||||
recipient_wallet: payout_wallet.as_str_api().to_string(),
|
||||
sender_item_id: format!("{}-{}", UserId::from(id), Utc::now().timestamp()),
|
||||
sender_item_id: format!(
|
||||
"{}-{}",
|
||||
UserId::from(id),
|
||||
Utc::now().timestamp()
|
||||
),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -760,8 +768,8 @@ pub async fn user_payouts_request(
|
||||
data.amount,
|
||||
"success"
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -780,8 +788,7 @@ pub async fn user_payouts_request(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInput(
|
||||
"You do not have enough funds to make this payout!"
|
||||
.to_string(),
|
||||
"You do not have enough funds to make this payout!".to_string(),
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::file_hosting::FileHost;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pack::PackFileHash;
|
||||
use crate::models::projects::{
|
||||
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId,
|
||||
Version, VersionFile, VersionId, VersionStatus, VersionType,
|
||||
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, Version, VersionFile,
|
||||
VersionId, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
@@ -97,11 +97,8 @@ pub async fn version_create(
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
let undo_result = super::project_creation::undo_uploads(
|
||||
&***file_host,
|
||||
&uploaded_files,
|
||||
)
|
||||
.await;
|
||||
let undo_result =
|
||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||
let rollback_result = transaction.rollback().await;
|
||||
|
||||
undo_result?;
|
||||
@@ -127,10 +124,8 @@ async fn version_create_inner(
|
||||
let mut initial_version_data = None;
|
||||
let mut version_builder = None;
|
||||
|
||||
let all_game_versions =
|
||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_loaders =
|
||||
models::categories::Loader::list(&mut *transaction).await?;
|
||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||
|
||||
let user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||
|
||||
@@ -145,9 +140,7 @@ async fn version_create_inner(
|
||||
let result = async {
|
||||
let content_disposition = field.content_disposition().clone();
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError(
|
||||
"Missing content name".to_string(),
|
||||
)
|
||||
CreateError::MissingValueError("Missing content name".to_string())
|
||||
})?;
|
||||
|
||||
if name == "data" {
|
||||
@@ -156,11 +149,9 @@ async fn version_create_inner(
|
||||
data.extend_from_slice(&chunk?);
|
||||
}
|
||||
|
||||
let version_create_data: InitialVersionData =
|
||||
serde_json::from_slice(&data)?;
|
||||
let version_create_data: InitialVersionData = serde_json::from_slice(&data)?;
|
||||
initial_version_data = Some(version_create_data);
|
||||
let version_create_data =
|
||||
initial_version_data.as_ref().unwrap();
|
||||
let version_create_data = initial_version_data.as_ref().unwrap();
|
||||
if version_create_data.project_id.is_none() {
|
||||
return Err(CreateError::MissingValueError(
|
||||
"Missing project id".to_string(),
|
||||
@@ -168,9 +159,7 @@ async fn version_create_inner(
|
||||
}
|
||||
|
||||
version_create_data.validate().map_err(|err| {
|
||||
CreateError::ValidationError(validation_errors_to_string(
|
||||
err, None,
|
||||
))
|
||||
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
if !version_create_data.status.can_be_requested() {
|
||||
@@ -179,8 +168,7 @@ async fn version_create_inner(
|
||||
));
|
||||
}
|
||||
|
||||
let project_id: models::ProjectId =
|
||||
version_create_data.project_id.unwrap().into();
|
||||
let project_id: models::ProjectId = version_create_data.project_id.unwrap().into();
|
||||
|
||||
// Ensure that the project this version is being added to exists
|
||||
let results = sqlx::query!(
|
||||
@@ -206,8 +194,7 @@ async fn version_create_inner(
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::CustomAuthenticationError(
|
||||
"You don't have permission to upload this version!"
|
||||
.to_string(),
|
||||
"You don't have permission to upload this version!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -216,13 +203,11 @@ async fn version_create_inner(
|
||||
.contains(Permissions::UPLOAD_VERSION)
|
||||
{
|
||||
return Err(CreateError::CustomAuthenticationError(
|
||||
"You don't have permission to upload this version!"
|
||||
.to_string(),
|
||||
"You don't have permission to upload this version!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let version_id: VersionId =
|
||||
models::generate_version_id(transaction).await?.into();
|
||||
let version_id: VersionId = models::generate_version_id(transaction).await?.into();
|
||||
|
||||
let project_type = sqlx::query!(
|
||||
"
|
||||
@@ -243,13 +228,10 @@ async fn version_create_inner(
|
||||
all_game_versions
|
||||
.iter()
|
||||
.find(|y| y.version == x.0)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidGameVersion(x.0.clone())
|
||||
})
|
||||
.ok_or_else(|| CreateError::InvalidGameVersion(x.0.clone()))
|
||||
.map(|y| y.id)
|
||||
})
|
||||
.collect::<Result<Vec<models::GameVersionId>, CreateError>>(
|
||||
)?;
|
||||
.collect::<Result<Vec<models::GameVersionId>, CreateError>>()?;
|
||||
|
||||
let loaders = version_create_data
|
||||
.loaders
|
||||
@@ -258,13 +240,9 @@ async fn version_create_inner(
|
||||
all_loaders
|
||||
.iter()
|
||||
.find(|y| {
|
||||
y.loader == x.0
|
||||
&& y.supported_project_types
|
||||
.contains(&project_type)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidLoader(x.0.clone())
|
||||
y.loader == x.0 && y.supported_project_types.contains(&project_type)
|
||||
})
|
||||
.ok_or_else(|| CreateError::InvalidLoader(x.0.clone()))
|
||||
.map(|y| y.id)
|
||||
})
|
||||
.collect::<Result<Vec<models::LoaderId>, CreateError>>()?;
|
||||
@@ -286,17 +264,12 @@ async fn version_create_inner(
|
||||
author_id: user.id.into(),
|
||||
name: version_create_data.version_title.clone(),
|
||||
version_number: version_create_data.version_number.clone(),
|
||||
changelog: version_create_data
|
||||
.version_body
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
changelog: version_create_data.version_body.clone().unwrap_or_default(),
|
||||
files: Vec::new(),
|
||||
dependencies,
|
||||
game_versions,
|
||||
loaders,
|
||||
version_type: version_create_data
|
||||
.release_channel
|
||||
.to_string(),
|
||||
version_type: version_create_data.release_channel.to_string(),
|
||||
featured: version_create_data.featured,
|
||||
status: version_create_data.status,
|
||||
requested_status: None,
|
||||
@@ -306,9 +279,7 @@ async fn version_create_inner(
|
||||
}
|
||||
|
||||
let version = version_builder.as_mut().ok_or_else(|| {
|
||||
CreateError::InvalidInput(String::from(
|
||||
"`data` field must come before file fields",
|
||||
))
|
||||
CreateError::InvalidInput(String::from("`data` field must come before file fields"))
|
||||
})?;
|
||||
|
||||
let project_type = sqlx::query!(
|
||||
@@ -323,12 +294,9 @@ async fn version_create_inner(
|
||||
.await?
|
||||
.name;
|
||||
|
||||
let version_data =
|
||||
initial_version_data.clone().ok_or_else(|| {
|
||||
CreateError::InvalidInput(
|
||||
"`data` field is required".to_string(),
|
||||
)
|
||||
})?;
|
||||
let version_data = initial_version_data
|
||||
.clone()
|
||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||
|
||||
upload_file(
|
||||
&mut field,
|
||||
@@ -365,12 +333,10 @@ async fn version_create_inner(
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let version_data = initial_version_data.ok_or_else(|| {
|
||||
CreateError::InvalidInput("`data` field is required".to_string())
|
||||
})?;
|
||||
let builder = version_builder.ok_or_else(|| {
|
||||
CreateError::InvalidInput("`data` field is required".to_string())
|
||||
})?;
|
||||
let version_data = initial_version_data
|
||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||
let builder = version_builder
|
||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||
|
||||
if builder.files.is_empty() {
|
||||
return Err(CreateError::InvalidInput(
|
||||
@@ -388,9 +354,7 @@ async fn version_create_inner(
|
||||
builder.project_id as crate::database::models::ids::ProjectId
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| models::ids::UserId(m.follower_id)))
|
||||
})
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| models::ids::UserId(m.follower_id))) })
|
||||
.try_collect::<Vec<models::ids::UserId>>()
|
||||
.await?;
|
||||
|
||||
@@ -453,8 +417,7 @@ async fn version_create_inner(
|
||||
let project_id = builder.project_id;
|
||||
builder.insert(transaction).await?;
|
||||
|
||||
models::Project::update_game_versions(project_id, &mut *transaction)
|
||||
.await?;
|
||||
models::Project::update_game_versions(project_id, &mut *transaction).await?;
|
||||
models::Project::update_loaders(project_id, &mut *transaction).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
@@ -486,11 +449,8 @@ pub async fn upload_file_to_version(
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
let undo_result = super::project_creation::undo_uploads(
|
||||
&***file_host,
|
||||
&uploaded_files,
|
||||
)
|
||||
.await;
|
||||
let undo_result =
|
||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||
let rollback_result = transaction.rollback().await;
|
||||
|
||||
undo_result?;
|
||||
@@ -541,8 +501,7 @@ async fn upload_file_to_version_inner(
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::CustomAuthenticationError(
|
||||
"You don't have permission to upload files to this version!"
|
||||
.to_string(),
|
||||
"You don't have permission to upload files to this version!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -551,8 +510,7 @@ async fn upload_file_to_version_inner(
|
||||
.contains(Permissions::UPLOAD_VERSION)
|
||||
{
|
||||
return Err(CreateError::CustomAuthenticationError(
|
||||
"You don't have permission to upload files to this version!"
|
||||
.to_string(),
|
||||
"You don't have permission to upload files to this version!".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -571,8 +529,7 @@ async fn upload_file_to_version_inner(
|
||||
.await?
|
||||
.name;
|
||||
|
||||
let all_game_versions =
|
||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
|
||||
let mut error = None;
|
||||
while let Some(item) = payload.next().await {
|
||||
@@ -585,9 +542,7 @@ async fn upload_file_to_version_inner(
|
||||
let result = async {
|
||||
let content_disposition = field.content_disposition().clone();
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError(
|
||||
"Missing content name".to_string(),
|
||||
)
|
||||
CreateError::MissingValueError("Missing content name".to_string())
|
||||
})?;
|
||||
|
||||
if name == "data" {
|
||||
@@ -602,9 +557,7 @@ async fn upload_file_to_version_inner(
|
||||
}
|
||||
|
||||
let file_data = initial_file_data.as_ref().ok_or_else(|| {
|
||||
CreateError::InvalidInput(String::from(
|
||||
"`data` field must come before file fields",
|
||||
))
|
||||
CreateError::InvalidInput(String::from("`data` field must come before file fields"))
|
||||
})?;
|
||||
|
||||
let mut dependencies = version
|
||||
@@ -703,9 +656,7 @@ pub async fn upload_file(
|
||||
}
|
||||
|
||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidFileType(file_extension.to_string())
|
||||
})?;
|
||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||
|
||||
let data = read_from_field(
|
||||
field, 500 * (1 << 20),
|
||||
@@ -731,8 +682,7 @@ pub async fn upload_file(
|
||||
|
||||
if exists {
|
||||
return Err(CreateError::InvalidInput(
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
||||
.to_string(),
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -761,23 +711,20 @@ pub async fn upload_file(
|
||||
.collect();
|
||||
|
||||
let res = sqlx::query!(
|
||||
"
|
||||
"
|
||||
SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h
|
||||
INNER JOIN files f on h.file_id = f.id
|
||||
INNER JOIN versions v on f.version_id = v.id
|
||||
WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)
|
||||
",
|
||||
&*hashes
|
||||
)
|
||||
.fetch_all(&mut *transaction).await?;
|
||||
&*hashes
|
||||
)
|
||||
.fetch_all(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
for file in &format.files {
|
||||
if let Some(dep) = res.iter().find(|x| {
|
||||
Some(&*x.hash)
|
||||
== file
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| x.as_bytes())
|
||||
Some(&*x.hash) == file.hashes.get(&PackFileHash::Sha1).map(|x| x.as_bytes())
|
||||
}) {
|
||||
dependencies.push(DependencyBuilder {
|
||||
project_id: Some(models::ProjectId(dep.project_id)),
|
||||
@@ -828,8 +775,7 @@ pub async fn upload_file(
|
||||
version_id,
|
||||
urlencoding::encode(file_name)
|
||||
);
|
||||
let file_path =
|
||||
format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||
let file_path = format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &file_path, data)
|
||||
@@ -849,8 +795,7 @@ pub async fn upload_file(
|
||||
.any(|y| y.hash == sha1_bytes || y.hash == sha512_bytes)
|
||||
}) {
|
||||
return Err(CreateError::InvalidInput(
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
||||
.to_string(),
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -888,9 +833,9 @@ pub async fn upload_file(
|
||||
pub fn get_name_ext(
|
||||
content_disposition: &actix_web::http::header::ContentDisposition,
|
||||
) -> Result<(&str, &str), CreateError> {
|
||||
let file_name = content_disposition.get_filename().ok_or_else(|| {
|
||||
CreateError::MissingValueError("Missing content file name".to_string())
|
||||
})?;
|
||||
let file_name = content_disposition
|
||||
.get_filename()
|
||||
.ok_or_else(|| CreateError::MissingValueError("Missing content file name".to_string()))?;
|
||||
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
||||
file_name.get((last_period + 1)..).unwrap_or("")
|
||||
} else {
|
||||
|
||||
@@ -84,8 +84,7 @@ pub async fn get_version_from_hash(
|
||||
.iter()
|
||||
.map(|x| database::models::VersionId(x.version_id))
|
||||
.collect::<Vec<_>>();
|
||||
let versions_data =
|
||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
|
||||
if let Some(first) = versions_data.first() {
|
||||
if hash_query.multiple {
|
||||
@@ -96,8 +95,7 @@ pub async fn get_version_from_hash(
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(HttpResponse::Ok()
|
||||
.json(models::projects::Version::from(first.clone())))
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(first.clone())))
|
||||
}
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
@@ -128,10 +126,16 @@ pub async fn download_version(
|
||||
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hash.as_bytes(),
|
||||
hash_query.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
@@ -178,28 +182,25 @@ pub async fn delete_file(
|
||||
|| Some(x.version_id) == hash_query.version_id.map(|x| x.0 as i64)
|
||||
}) {
|
||||
if !user.role.is_admin() {
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_version(
|
||||
database::models::ids::VersionId(row.version_id),
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||
database::models::ids::VersionId(row.version_id),
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to delete this file!".to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to delete this file!"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
})?;
|
||||
|
||||
if !team_member
|
||||
.permissions
|
||||
.contains(Permissions::DELETE_VERSION)
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to delete this file!"
|
||||
.to_string(),
|
||||
"You don't have permission to delete this file!".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -220,8 +221,7 @@ pub async fn delete_file(
|
||||
|
||||
if files.len() < 2 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Versions must have at least one file uploaded to them"
|
||||
.to_string(),
|
||||
"Versions must have at least one file uploaded to them".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -324,9 +324,7 @@ pub async fn get_update_from_hash(
|
||||
.await?;
|
||||
|
||||
if let Some(version_id) = version_ids.first() {
|
||||
let version_data =
|
||||
database::models::Version::get_full(*version_id, &**pool)
|
||||
.await?;
|
||||
let version_data = database::models::Version::get_full(*version_id, &**pool).await?;
|
||||
|
||||
ok_or_not_found::<QueryVersion, Version>(version_data)
|
||||
} else {
|
||||
@@ -364,10 +362,16 @@ pub async fn get_versions_from_hashes(
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hashes_parsed.as_slice(),
|
||||
file_data.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_all(&**pool)
|
||||
.await?;
|
||||
@@ -376,8 +380,7 @@ pub async fn get_versions_from_hashes(
|
||||
.iter()
|
||||
.map(|x| database::models::VersionId(x.version_id))
|
||||
.collect::<Vec<_>>();
|
||||
let versions_data =
|
||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
|
||||
let response: Result<HashMap<String, Version>, ApiError> = result
|
||||
.into_iter()
|
||||
@@ -388,10 +391,7 @@ pub async fn get_versions_from_hashes(
|
||||
.find(|x| x.inner.id.0 == row.version_id)
|
||||
.map(|v| {
|
||||
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
||||
Ok((
|
||||
parsed_hash,
|
||||
crate::models::projects::Version::from(v),
|
||||
))
|
||||
Ok((parsed_hash, crate::models::projects::Version::from(v)))
|
||||
} else {
|
||||
Err(ApiError::Database(DatabaseError::Other(format!(
|
||||
"Could not parse hash for version {}",
|
||||
@@ -423,20 +423,25 @@ pub async fn get_projects_from_hashes(
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hashes_parsed.as_slice(),
|
||||
file_data.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_all(&**pool)
|
||||
.await?;
|
||||
.fetch_all(&**pool)
|
||||
.await?;
|
||||
|
||||
let project_ids = result
|
||||
.iter()
|
||||
.map(|x| database::models::ProjectId(x.project_id))
|
||||
.collect::<Vec<_>>();
|
||||
let versions_data =
|
||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
let versions_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||
|
||||
let response: Result<HashMap<String, Project>, ApiError> = result
|
||||
.into_iter()
|
||||
@@ -447,10 +452,7 @@ pub async fn get_projects_from_hashes(
|
||||
.find(|x| x.inner.id.0 == row.project_id)
|
||||
.map(|v| {
|
||||
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
||||
Ok((
|
||||
parsed_hash,
|
||||
crate::models::projects::Project::from(v),
|
||||
))
|
||||
Ok((parsed_hash, crate::models::projects::Project::from(v)))
|
||||
} else {
|
||||
Err(ApiError::Database(DatabaseError::Other(format!(
|
||||
"Could not parse hash for version {}",
|
||||
@@ -538,20 +540,26 @@ pub async fn update_files(
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hashes_parsed.as_slice(),
|
||||
update_data.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id))))
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right()
|
||||
.map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id))))
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let mut version_ids: HashMap<database::models::VersionId, Vec<u8>> =
|
||||
HashMap::new();
|
||||
let mut version_ids: HashMap<database::models::VersionId, Vec<u8>> = HashMap::new();
|
||||
|
||||
let updated_versions = database::models::Version::get_projects_versions(
|
||||
result
|
||||
@@ -583,17 +591,13 @@ pub async fn update_files(
|
||||
.await?;
|
||||
|
||||
for (hash, id) in result {
|
||||
if let Some(latest_version) =
|
||||
updated_versions.get(&id).and_then(|x| x.last())
|
||||
{
|
||||
if let Some(latest_version) = updated_versions.get(&id).and_then(|x| x.last()) {
|
||||
version_ids.insert(*latest_version, hash);
|
||||
}
|
||||
}
|
||||
|
||||
let query_version_ids = version_ids.keys().copied().collect::<Vec<_>>();
|
||||
let versions =
|
||||
database::models::Version::get_many_full(&query_version_ids, &**pool)
|
||||
.await?;
|
||||
let versions = database::models::Version::get_many_full(&query_version_ids, &**pool).await?;
|
||||
|
||||
let mut response = HashMap::new();
|
||||
|
||||
@@ -602,10 +606,7 @@ pub async fn update_files(
|
||||
|
||||
if let Some(hash) = hash {
|
||||
if let Ok(parsed_hash) = String::from_utf8(hash.clone()) {
|
||||
response.insert(
|
||||
parsed_hash,
|
||||
models::projects::Version::from(version),
|
||||
);
|
||||
response.insert(parsed_hash, models::projects::Version::from(version));
|
||||
} else {
|
||||
let version_id: VersionId = version.inner.id.into();
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
use super::ApiError;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::projects::{
|
||||
Dependency, FileType, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::{
|
||||
filter_authorized_versions, get_user_from_headers, is_authorized,
|
||||
is_authorized_version,
|
||||
filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version,
|
||||
};
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
@@ -49,10 +46,7 @@ pub async fn version_list(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get_from_slug_or_project_id(
|
||||
&string, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
@@ -80,9 +74,7 @@ pub async fn version_list(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut versions =
|
||||
database::models::Version::get_many_full(&version_ids, &**pool)
|
||||
.await?;
|
||||
let mut versions = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
|
||||
let mut response = versions
|
||||
.iter()
|
||||
@@ -95,22 +87,13 @@ pub async fn version_list(
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
versions.sort_by(|a, b| {
|
||||
b.inner.date_published.cmp(&a.inner.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()
|
||||
&& !versions.is_empty()
|
||||
&& filters.featured.unwrap_or(false)
|
||||
{
|
||||
if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) {
|
||||
let (loaders, game_versions) = futures::future::try_join(
|
||||
database::models::categories::Loader::list(&**pool),
|
||||
database::models::categories::GameVersion::list_filter(
|
||||
None,
|
||||
Some(true),
|
||||
&**pool,
|
||||
),
|
||||
database::models::categories::GameVersion::list_filter(None, Some(true), &**pool),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -139,13 +122,10 @@ pub async fn version_list(
|
||||
}
|
||||
}
|
||||
|
||||
response.sort_by(|a, b| {
|
||||
b.inner.date_published.cmp(&a.inner.date_published)
|
||||
});
|
||||
response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
||||
|
||||
let response =
|
||||
filter_authorized_versions(response, &user_option, &pool).await?;
|
||||
let response = filter_authorized_versions(response, &user_option, &pool).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
@@ -162,16 +142,13 @@ pub async fn version_project_get(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner();
|
||||
let version_data =
|
||||
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool)
|
||||
.await?;
|
||||
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
if let Some(data) = version_data {
|
||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||
);
|
||||
return Ok(HttpResponse::Ok().json(models::projects::Version::from(data)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,18 +166,15 @@ pub async fn versions_get(
|
||||
web::Query(ids): web::Query<VersionIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let version_ids =
|
||||
serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<database::models::VersionId>>();
|
||||
let versions_data =
|
||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<database::models::VersionId>>();
|
||||
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let versions =
|
||||
filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
||||
let versions = filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(versions))
|
||||
}
|
||||
@@ -212,16 +186,13 @@ pub async fn version_get(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
let version_data =
|
||||
database::models::Version::get_full(id.into(), &**pool).await?;
|
||||
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 {
|
||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||
);
|
||||
return Ok(HttpResponse::Ok().json(models::projects::Version::from(data)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,9 +244,9 @@ pub async fn version_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_version.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
new_version
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let version_id = info.into_inner().0;
|
||||
let id = version_id.into();
|
||||
@@ -283,19 +254,15 @@ pub async fn version_edit(
|
||||
let result = database::models::Version::get_full(id, &**pool).await?;
|
||||
|
||||
if let Some(version_item) = result {
|
||||
let project_item = database::models::Project::get_full(
|
||||
version_item.inner.project_id,
|
||||
let project_item =
|
||||
database::models::Project::get_full(version_item.inner.project_id, &**pool).await?;
|
||||
|
||||
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||
version_item.inner.id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_version(
|
||||
version_item.inner.id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let permissions;
|
||||
|
||||
if user.role.is_admin() {
|
||||
@@ -303,8 +270,7 @@ pub async fn version_edit(
|
||||
} else if let Some(member) = team_member {
|
||||
permissions = Some(member.permissions)
|
||||
} else if user.role.is_mod() {
|
||||
permissions =
|
||||
Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||
permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||
} else {
|
||||
permissions = None
|
||||
}
|
||||
@@ -312,8 +278,7 @@ pub async fn version_edit(
|
||||
if let Some(perms) = permissions {
|
||||
if !perms.contains(Permissions::UPLOAD_VERSION) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit this version!"
|
||||
.to_string(),
|
||||
"You do not have the permissions to edit this version!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -403,18 +368,16 @@ pub async fn version_edit(
|
||||
.await?;
|
||||
|
||||
for game_version in game_versions {
|
||||
let game_version_id =
|
||||
database::models::categories::GameVersion::get_id(
|
||||
&game_version.0,
|
||||
&mut *transaction,
|
||||
let game_version_id = database::models::categories::GameVersion::get_id(
|
||||
&game_version.0,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"No database entry for game version provided.".to_string(),
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"No database entry for game version provided."
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -447,17 +410,13 @@ pub async fn version_edit(
|
||||
|
||||
for loader in loaders {
|
||||
let loader_id =
|
||||
database::models::categories::Loader::get_id(
|
||||
&loader.0,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"No database entry for loader provided."
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
database::models::categories::Loader::get_id(&loader.0, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"No database entry for loader provided.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -551,8 +510,7 @@ pub async fn version_edit(
|
||||
if let Some(downloads) = &new_version.downloads {
|
||||
if !user.role.is_mod() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set the downloads of this mod"
|
||||
.to_string(),
|
||||
"You don't have permission to set the downloads of this mod".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -577,8 +535,7 @@ pub async fn version_edit(
|
||||
WHERE (id = $2)
|
||||
",
|
||||
diff as i32,
|
||||
version_item.inner.project_id
|
||||
as database::models::ids::ProjectId,
|
||||
version_item.inner.project_id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -667,8 +624,7 @@ pub async fn version_schedule(
|
||||
|
||||
if scheduling_data.time < Utc::now() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You cannot schedule a version to be released in the past!"
|
||||
.to_string(),
|
||||
"You cannot schedule a version to be released in the past!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -679,17 +635,15 @@ pub async fn version_schedule(
|
||||
}
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result =
|
||||
database::models::Version::get_full(string.into(), &**pool).await?;
|
||||
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?;
|
||||
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
|
||||
@@ -748,17 +702,14 @@ pub async fn version_delete(
|
||||
.contains(Permissions::DELETE_VERSION)
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to delete versions in this team"
|
||||
.to_string(),
|
||||
"You do not have permission to delete versions in this team".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result =
|
||||
database::models::Version::remove_full(id.into(), &mut transaction)
|
||||
.await?;
|
||||
let result = database::models::Version::remove_full(id.into(), &mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user