1
0

Run fmt, fix dep route (#312)

This commit is contained in:
Geometrically
2022-02-27 21:44:00 -07:00
committed by GitHub
parent 725f8571bb
commit 459e36c027
53 changed files with 1798 additions and 867 deletions

View File

@@ -38,14 +38,26 @@ pub enum AuthorizationError {
impl actix_web::ResponseError for AuthorizationError {
fn status_code(&self) -> StatusCode {
match self {
AuthorizationError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthorizationError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthorizationError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthorizationError::EnvError(..) => {
StatusCode::INTERNAL_SERVER_ERROR
}
AuthorizationError::SqlxDatabaseError(..) => {
StatusCode::INTERNAL_SERVER_ERROR
}
AuthorizationError::DatabaseError(..) => {
StatusCode::INTERNAL_SERVER_ERROR
}
AuthorizationError::SerDeError(..) => StatusCode::BAD_REQUEST,
AuthorizationError::GithubError(..) => StatusCode::FAILED_DEPENDENCY,
AuthorizationError::InvalidCredentialsError => StatusCode::UNAUTHORIZED,
AuthorizationError::GithubError(..) => {
StatusCode::FAILED_DEPENDENCY
}
AuthorizationError::InvalidCredentialsError => {
StatusCode::UNAUTHORIZED
}
AuthorizationError::DecodingError(..) => StatusCode::BAD_REQUEST,
AuthorizationError::AuthenticationError(..) => StatusCode::UNAUTHORIZED,
AuthorizationError::AuthenticationError(..) => {
StatusCode::UNAUTHORIZED
}
}
}
@@ -57,9 +69,13 @@ impl actix_web::ResponseError for AuthorizationError {
AuthorizationError::DatabaseError(..) => "database_error",
AuthorizationError::SerDeError(..) => "invalid_input",
AuthorizationError::GithubError(..) => "github_error",
AuthorizationError::InvalidCredentialsError => "invalid_credentials",
AuthorizationError::InvalidCredentialsError => {
"invalid_credentials"
}
AuthorizationError::DecodingError(..) => "decoding_error",
AuthorizationError::AuthenticationError(..) => "authentication_error",
AuthorizationError::AuthenticationError(..) => {
"authentication_error"
}
},
description: &self.to_string(),
})
@@ -174,11 +190,14 @@ 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 => {
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;

View File

@@ -58,7 +58,11 @@ pub async fn maven_metadata(
) -> Result<HttpResponse, ApiError> {
let project_id = params.into_inner().0;
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 data = if let Some(data) = project_data {
data
@@ -119,7 +123,9 @@ 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);
}
@@ -129,7 +135,9 @@ fn find_file<'a>(
_ => return None,
};
if file == format!("{}-{}.{}", &project_id, &version.version_number, fileext) {
if file
== format!("{}-{}.{}", &project_id, &version.version_number, fileext)
{
version
.files
.iter()
@@ -148,7 +156,11 @@ 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
@@ -175,9 +187,11 @@ 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 {
@@ -197,10 +211,12 @@ pub async fn version_file(
name: project.inner.title,
description: project.inner.description,
};
return Ok(HttpResponse::Ok()
.content_type("text/xml")
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?));
} else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) {
return Ok(HttpResponse::Ok().content_type("text/xml").body(
yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?,
));
} else if let Some(selected_file) =
find_file(&project_id, &project, &version, &file)
{
return Ok(HttpResponse::TemporaryRedirect()
.append_header(("location", &*selected_file.url))
.body(""));
@@ -217,7 +233,11 @@ 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
@@ -244,9 +264,11 @@ 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 {
@@ -268,7 +290,11 @@ 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
@@ -295,9 +321,11 @@ 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 {

View File

@@ -187,38 +187,62 @@ pub enum ApiError {
impl actix_web::ResponseError for ApiError {
fn status_code(&self) -> actix_web::http::StatusCode {
match self {
ApiError::EnvError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::DatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::SqlxDatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::AuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
ApiError::CustomAuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
ApiError::XmlError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::EnvError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::DatabaseError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::SqlxDatabaseError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::AuthenticationError(..) => {
actix_web::http::StatusCode::UNAUTHORIZED
}
ApiError::CustomAuthenticationError(..) => {
actix_web::http::StatusCode::UNAUTHORIZED
}
ApiError::XmlError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::SearchError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::IndexingError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::FileHostingError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
ApiError::InvalidInputError(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::ValidationError(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::SearchError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::IndexingError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::FileHostingError(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::InvalidInputError(..) => {
actix_web::http::StatusCode::BAD_REQUEST
}
ApiError::ValidationError(..) => {
actix_web::http::StatusCode::BAD_REQUEST
}
}
}
fn error_response(&self) -> actix_web::HttpResponse {
actix_web::HttpResponse::build(self.status_code()).json(crate::models::error::ApiError {
error: match self {
ApiError::EnvError(..) => "environment_error",
ApiError::SqlxDatabaseError(..) => "database_error",
ApiError::DatabaseError(..) => "database_error",
ApiError::AuthenticationError(..) => "unauthorized",
ApiError::CustomAuthenticationError(..) => "unauthorized",
ApiError::XmlError(..) => "xml_error",
ApiError::JsonError(..) => "json_error",
ApiError::SearchError(..) => "search_error",
ApiError::IndexingError(..) => "indexing_error",
ApiError::FileHostingError(..) => "file_hosting_error",
ApiError::InvalidInputError(..) => "invalid_input",
ApiError::ValidationError(..) => "invalid_input",
actix_web::HttpResponse::build(self.status_code()).json(
crate::models::error::ApiError {
error: match self {
ApiError::EnvError(..) => "environment_error",
ApiError::SqlxDatabaseError(..) => "database_error",
ApiError::DatabaseError(..) => "database_error",
ApiError::AuthenticationError(..) => "unauthorized",
ApiError::CustomAuthenticationError(..) => "unauthorized",
ApiError::XmlError(..) => "xml_error",
ApiError::JsonError(..) => "json_error",
ApiError::SearchError(..) => "search_error",
ApiError::IndexingError(..) => "indexing_error",
ApiError::FileHostingError(..) => "file_hosting_error",
ApiError::InvalidInputError(..) => "invalid_input",
ApiError::ValidationError(..) => "invalid_input",
},
description: &self.to_string(),
},
description: &self.to_string(),
})
)
}
}

View File

@@ -39,15 +39,18 @@ 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))
}

View File

@@ -31,8 +31,11 @@ pub async fn notifications_get(
.collect();
let notifications_data: Vec<DBNotification> =
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
.await?;
database::models::notification_item::Notification::get_many(
notification_ids,
&**pool,
)
.await?;
let notifications: Vec<Notification> = notifications_data
.into_iter()
@@ -54,7 +57,11 @@ 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_mod() {
@@ -78,21 +85,29 @@ 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_mod() {
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::CustomAuthenticationError(
"You are not authorized to delete this notification!".to_string(),
"You are not authorized to delete this notification!"
.to_string(),
))
}
} else {
@@ -108,18 +123,23 @@ 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();
let notification_ids =
serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let mut transaction = pool.begin().await?;
let notifications_data =
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
.await?;
database::models::notification_item::Notification::get_many(
notification_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_mod() {
@@ -127,8 +147,11 @@ pub async fn notifications_delete(
}
}
database::models::notification_item::Notification::remove_many(notifications, &mut transaction)
.await?;
database::models::notification_item::Notification::remove_many(
notifications,
&mut transaction,
)
.await?;
transaction.commit().await?;

View File

@@ -67,10 +67,14 @@ 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,
@@ -81,7 +85,9 @@ 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,
@@ -297,17 +303,21 @@ pub async fn project_create_inner(
let cdn_url = dotenv::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
@@ -324,9 +334,9 @@ pub 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(
@@ -336,19 +346,22 @@ pub 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)
@@ -393,15 +406,17 @@ pub async fn project_create_inner(
project_create_data = create_data;
}
let project_type_id =
models::ProjectTypeId::get_id(project_create_data.project_type.clone(), &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(format!(
"Project Type {} does not exist.",
project_create_data.project_type.clone()
))
})?;
let project_type_id = models::ProjectTypeId::get_id(
project_create_data.project_type.clone(),
&mut *transaction,
)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(format!(
"Project Type {} does not exist.",
project_create_data.project_type.clone()
))
})?;
let mut icon_url = None;
@@ -409,9 +424,9 @@ pub async fn project_create_inner(
let mut field: Field = item.map_err(CreateError::MultipartError)?;
let content_disposition = field.content_disposition().clone();
let name = content_disposition
.get_name()
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
let name = content_disposition.get_name().ok_or_else(|| {
CreateError::MissingValueError("Missing content name".to_string())
})?;
let (file_name, file_extension) =
super::version_creation::get_name_ext(&content_disposition)?;
@@ -454,11 +469,21 @@ pub 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)
.ok_or_else(|| CreateError::InvalidIconFormat(file_extension.to_string()))?;
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(),
)
})?;
let url = format!("data/{}/images/{}.{}", project_id, hash, file_extension);
let url = format!(
"data/{}/images/{}.{}",
project_id, hash, file_extension
);
let upload_data = file_host
.upload_file(content_type, &url, data.freeze())
.await?;
@@ -491,7 +516,8 @@ pub 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(
@@ -529,7 +555,8 @@ pub 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,
@@ -568,44 +595,58 @@ pub async fn project_create_inner(
let status_id = models::StatusId::get_id(&status, &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(format!("Status {} does not exist.", status.clone()))
CreateError::InvalidInput(format!(
"Status {} does not exist.",
status.clone()
))
})?;
let client_side_id =
models::SideTypeId::get_id(&project_create_data.client_side, &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(
"Client side type specified does not exist.".to_string(),
)
})?;
let client_side_id = models::SideTypeId::get_id(
&project_create_data.client_side,
&mut *transaction,
)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(
"Client side type specified does not exist.".to_string(),
)
})?;
let server_side_id =
models::SideTypeId::get_id(&project_create_data.server_side, &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(
"Server side type specified does not exist.".to_string(),
)
})?;
let server_side_id = models::SideTypeId::get_id(
&project_create_data.server_side,
&mut *transaction,
)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(
"Server side type specified does not exist.".to_string(),
)
})?;
let license_id =
models::categories::License::get_id(&project_create_data.license_id, &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::InvalidInput("License specified does not exist.".to_string())
})?;
let license_id = models::categories::License::get_id(
&project_create_data.license_id,
&mut *transaction,
)
.await?
.ok_or_else(|| {
CreateError::InvalidInput(
"License specified does not exist.".to_string(),
)
})?;
let mut donation_urls = vec![];
if let Some(urls) = &project_create_data.donation_urls {
for url in urls {
let platform_id = models::DonationPlatformId::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::DonationPlatformId::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 {
project_id: project_id.into(),
@@ -695,9 +736,12 @@ pub async fn project_create_inner(
if status == ProjectStatus::Processing {
if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") {
crate::util::webhook::send_discord_webhook(response.clone(), webhook_url)
.await
.ok();
crate::util::webhook::send_discord_webhook(
response.clone(),
webhook_url,
)
.await
.ok();
}
}
@@ -720,12 +764,13 @@ 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
@@ -794,8 +839,15 @@ async fn process_icon_upload(
mut field: actix_multipart::Field,
cdn_url: &str,
) -> Result<String, 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 upload_data = file_host
.upload_file(

View File

@@ -39,12 +39,14 @@ pub async fn projects_get(
web::Query(ids): web::Query<ProjectIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let project_ids =
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.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();
@@ -71,7 +73,10 @@ 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();
@@ -96,7 +101,9 @@ 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?;
if let Some(project) = result {
let id = project.id;
@@ -105,9 +112,10 @@ pub async fn dependency_list(
let dependencies = sqlx::query!(
"
SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id
SELECT d.dependency_id, vd.mod_id
FROM versions v
INNER JOIN dependencies d ON d.dependent_id = v.id
INNER JOIN versions vd ON d.dependency_id = vd.id
WHERE v.mod_id = $1
",
id as database::models::ProjectId
@@ -116,26 +124,24 @@ pub async fn dependency_list(
.try_filter_map(|e| async {
Ok(e.right().map(|x| {
(
database::models::VersionId(x.dependent_id),
x.dependency_id.map(database::models::VersionId),
x.mod_dependency_id.map(database::models::ProjectId),
database::models::ProjectId(x.mod_id),
)
}))
})
.try_collect::<Vec<(
database::models::VersionId,
Option<database::models::VersionId>,
Option<database::models::ProjectId>,
database::models::ProjectId,
)>>()
.await?;
let (projects_result, versions_result) = futures::join!(
database::Project::get_many_full(
dependencies.iter().map(|x| x.2).flatten().collect(),
dependencies.iter().map(|x| x.1).collect(),
&**pool,
),
database::Version::get_many_full(
dependencies.iter().map(|x| x.1).flatten().collect(),
dependencies.iter().map(|x| x.0).flatten().collect(),
&**pool,
)
);
@@ -244,13 +250,15 @@ 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::ValidationError(validation_errors_to_string(err, None)))?;
new_project.validate().map_err(|err| {
ApiError::ValidationError(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;
@@ -324,11 +332,13 @@ pub async fn project_edit(
));
}
if (status == &ProjectStatus::Rejected || status == &ProjectStatus::Approved)
if (status == &ProjectStatus::Rejected
|| status == &ProjectStatus::Approved)
&& !user.role.is_mod()
{
return Err(ApiError::CustomAuthenticationError(
"You don't have permission to set this status".to_string(),
"You don't have permission to set this status"
.to_string(),
));
}
@@ -361,7 +371,9 @@ pub async fn project_edit(
.execute(&mut *transaction)
.await?;
if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") {
if let Ok(webhook_url) =
dotenv::var("MODERATION_DISCORD_WEBHOOK")
{
crate::util::webhook::send_discord_webhook(
Project::from(project_item.clone()),
webhook_url,
@@ -371,13 +383,16 @@ pub async fn project_edit(
}
}
let status_id = database::models::StatusId::get_id(status, &mut *transaction)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"No database entry for status provided.".to_string(),
)
})?;
let status_id = database::models::StatusId::get_id(
status,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"No database entry for status provided.".to_string(),
)
})?;
sqlx::query!(
"
@@ -391,12 +406,19 @@ pub async fn project_edit(
.execute(&mut *transaction)
.await?;
if project_item.status.is_searchable() && !status.is_searchable() {
if project_item.status.is_searchable()
&& !status.is_searchable()
{
delete_from_index(id.into(), config).await?;
} else if !project_item.status.is_searchable() && status.is_searchable() {
} else if !project_item.status.is_searchable()
&& status.is_searchable()
{
let index_project =
crate::search::indexing::local_import::query_one(id, &mut *transaction)
.await?;
crate::search::indexing::local_import::query_one(
id,
&mut *transaction,
)
.await?;
indexing_queue.add(index_project);
}
@@ -422,14 +444,17 @@ 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::InvalidInputError(format!(
"Category {} does not exist.",
category.clone()
))
})?;
database::models::categories::Category::get_id(
category,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
"Category {} does not exist.",
category.clone()
))
})?;
sqlx::query!(
"
@@ -574,7 +599,8 @@ pub async fn project_edit(
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInputError(
"Slug collides with other project's id!".to_string(),
"Slug collides with other project's id!"
.to_string(),
));
}
}
@@ -601,10 +627,12 @@ pub async fn project_edit(
));
}
let side_type_id =
database::models::SideTypeId::get_id(new_side, &mut *transaction)
.await?
.expect("No database entry found for side type");
let side_type_id = database::models::SideTypeId::get_id(
new_side,
&mut *transaction,
)
.await?
.expect("No database entry found for side type");
sqlx::query!(
"
@@ -627,10 +655,12 @@ pub async fn project_edit(
));
}
let side_type_id =
database::models::SideTypeId::get_id(new_side, &mut *transaction)
.await?
.expect("No database entry found for side type");
let side_type_id = database::models::SideTypeId::get_id(
new_side,
&mut *transaction,
)
.await?
.expect("No database entry found for side type");
sqlx::query!(
"
@@ -653,10 +683,12 @@ pub async fn project_edit(
));
}
let license_id =
database::models::categories::License::get_id(license, &mut *transaction)
.await?
.expect("No database entry found for license");
let license_id = database::models::categories::License::get_id(
license,
&mut *transaction,
)
.await?
.expect("No database entry found for license");
sqlx::query!(
"
@@ -690,17 +722,18 @@ pub async fn project_edit(
.await?;
for donation in donations {
let platform_id = database::models::DonationPlatformId::get_id(
&donation.id,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
"Platform {} does not exist.",
donation.id.clone()
))
})?;
let platform_id =
database::models::DonationPlatformId::get_id(
&donation.id,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
"Platform {} does not exist.",
donation.id.clone()
))
})?;
sqlx::query!(
"
@@ -717,7 +750,9 @@ pub async fn project_edit(
}
if let Some(moderation_message) = &new_project.moderation_message {
if !user.role.is_mod() && project_item.status != ProjectStatus::Approved {
if !user.role.is_mod()
&& project_item.status != ProjectStatus::Approved
{
return Err(ApiError::CustomAuthenticationError(
"You do not have the permissions to edit the moderation message of this project!"
.to_string(),
@@ -737,8 +772,12 @@ pub async fn project_edit(
.await?;
}
if let Some(moderation_message_body) = &new_project.moderation_message_body {
if !user.role.is_mod() && project_item.status != ProjectStatus::Approved {
if let Some(moderation_message_body) =
&new_project.moderation_message_body
{
if !user.role.is_mod()
&& project_item.status != ProjectStatus::Approved
{
return Err(ApiError::CustomAuthenticationError(
"You do not have the permissions to edit the moderation message body of this project!"
.to_string(),
@@ -805,17 +844,24 @@ 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 = dotenv::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.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id(
@@ -826,12 +872,15 @@ pub async fn project_icon_edit(
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
}
@@ -844,8 +893,12 @@ 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 hash = sha1::Sha1::from(&bytes).hexdigest();
let project_id: ProjectId = project_item.id.into();
let upload_data = file_host
@@ -891,12 +944,16 @@ 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.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
let project_item = database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id(
@@ -907,12 +964,15 @@ pub async fn delete_project_icon(
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
}
@@ -962,20 +1022,28 @@ 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::ValidationError(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::ValidationError(validation_errors_to_string(err, None))
})?;
let cdn_url = dotenv::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.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id(
@@ -986,12 +1054,15 @@ pub async fn add_gallery_item(
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
}
@@ -1079,15 +1150,20 @@ 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::ValidationError(validation_errors_to_string(err, None)))?;
item.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None))
})?;
let project_item =
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
let project_item = database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id(
@@ -1098,12 +1174,15 @@ pub async fn edit_gallery_item(
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
}
@@ -1203,12 +1282,16 @@ 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.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
let project_item = database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id(
@@ -1219,12 +1302,15 @@ pub async fn delete_gallery_item(
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
}
@@ -1280,23 +1366,31 @@ 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.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("The specified project does not exist!".to_string())
})?;
let project = database::models::Project::get_from_slug_or_project_id(
string.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !user.role.is_mod() {
let team_member = database::models::TeamMember::get_from_user_id_project(
project.id,
user.id.into(),
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError("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::DatabaseError)?
.ok_or_else(|| {
ApiError::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
if !team_member
.permissions
@@ -1310,7 +1404,9 @@ 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?;
@@ -1332,11 +1428,14 @@ 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::InvalidInputError("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::InvalidInputError(
"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;
@@ -1397,11 +1496,14 @@ 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::InvalidInputError("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::InvalidInputError(
"The specified project does not exist!".to_string(),
)
})?;
let user_id: database::models::ids::UserId = user.id.into();
let project_id = result.id;
@@ -1457,9 +1559,11 @@ pub async fn delete_from_index(
id: crate::models::projects::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: Vec<meilisearch_sdk::indexes::Index> = client.get_indexes().await?;
let indexes: Vec<meilisearch_sdk::indexes::Index> =
client.get_indexes().await?;
for index in indexes {
index.delete_document(format!("{}", id)).await?;
}

View File

@@ -1,7 +1,9 @@
use crate::models::ids::{ProjectId, UserId, VersionId};
use crate::models::reports::{ItemType, Report};
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::StreamExt;
use serde::Deserialize;
@@ -23,24 +25,31 @@ 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::InvalidInputError("Error while parsing request payload!".to_string())
ApiError::InvalidInputError(
"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::InvalidInputError(format!("Invalid report type: {}", new_report.report_type))
ApiError::InvalidInputError(format!(
"Invalid report type: {}",
new_report.report_type
))
})?;
let mut report = crate::database::models::report_item::Report {
id,
@@ -56,17 +65,29 @@ pub async fn report_create(
match new_report.item_type {
ItemType::Project => {
report.project_id = Some(
serde_json::from_str::<ProjectId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<ProjectId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Version => {
report.version_id = Some(
serde_json::from_str::<VersionId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<VersionId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::User => {
report.user_id = Some(
serde_json::from_str::<UserId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<UserId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Unknown => {
@@ -127,8 +148,10 @@ pub async fn reports(
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
.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();

View File

@@ -1,6 +1,8 @@
use super::ApiError;
use crate::database::models;
use crate::database::models::categories::{DonationPlatform, License, ProjectType, ReportType};
use crate::database::models::categories::{
DonationPlatform, License, ProjectType, ReportType,
};
use crate::util::auth::check_is_admin_from_headers;
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
use models::categories::{Category, GameVersion, Loader};
@@ -40,7 +42,9 @@ 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 mut results = Category::list(&**pool)
.await?
.into_iter()
@@ -64,12 +68,16 @@ pub async fn category_create(
) -> Result<HttpResponse, ApiError> {
check_is_admin_from_headers(req.headers(), &**pool).await?;
let project_type =
crate::database::models::ProjectTypeId::get_id(new_category.project_type.clone(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("Specified project type does not exist!".to_string())
})?;
let project_type = crate::database::models::ProjectTypeId::get_id(
new_category.project_type.clone(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"Specified project type does not exist!".to_string(),
)
})?;
let _id = Category::builder()
.name(&new_category.name)?
@@ -90,7 +98,8 @@ pub async fn category_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = category.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = Category::remove(&name, &mut transaction).await?;
@@ -114,7 +123,9 @@ 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()
@@ -140,13 +151,18 @@ pub async fn loader_create(
let mut transaction = pool.begin().await?;
let project_types =
ProjectType::get_many_id(&new_loader.supported_project_types, &mut *transaction).await?;
let project_types = ProjectType::get_many_id(
&new_loader.supported_project_types,
&mut *transaction,
)
.await?;
let _id = Loader::builder()
.name(&new_loader.name)?
.icon(&new_loader.icon)?
.supported_project_types(&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>())?
.supported_project_types(
&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>(),
)?
.insert(&mut transaction)
.await?;
@@ -164,7 +180,8 @@ pub async fn loader_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = loader.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = Loader::remove(&name, &mut transaction).await?;
@@ -200,8 +217,11 @@ 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?
}
@@ -260,7 +280,8 @@ pub async fn game_version_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = game_version.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = GameVersion::remove(&name, &mut transaction).await?;
@@ -283,7 +304,9 @@ pub struct LicenseQueryData {
}
#[get("license")]
pub async fn license_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn license_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let results: Vec<LicenseQueryData> = License::list(&**pool)
.await?
.into_iter()
@@ -329,7 +352,8 @@ pub async fn license_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = license.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = License::remove(&name, &mut transaction).await?;
@@ -352,15 +376,18 @@ 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))
}
@@ -398,7 +425,8 @@ pub async fn donation_platform_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = loader.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = DonationPlatform::remove(&name, &mut transaction).await?;
@@ -415,7 +443,9 @@ pub async fn donation_platform_delete(
}
#[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))
}
@@ -444,7 +474,8 @@ pub async fn report_type_delete(
check_is_admin_from_headers(req.headers(), &**pool).await?;
let name = report_type.into_inner().0;
let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?;
let mut transaction =
pool.begin().await.map_err(models::DatabaseError::from)?;
let result = ReportType::remove(&name, &mut transaction).await?;

View File

@@ -1,4 +1,6 @@
use crate::database::models::notification_item::{NotificationActionBuilder, NotificationBuilder};
use crate::database::models::notification_item::{
NotificationActionBuilder, NotificationBuilder,
};
use crate::database::models::TeamMember;
use crate::models::ids::ProjectId;
use crate::models::teams::{Permissions, TeamId};
@@ -17,23 +19,33 @@ 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::DatabaseError)?;
let team_member = TeamMember::get_from_user_id(
project.team_id,
user.id.into(),
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?;
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));
@@ -59,14 +71,16 @@ 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) = current_user {
let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
.await
.map_err(ApiError::DatabaseError)?;
let team_member =
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
.await
.map_err(ApiError::DatabaseError)?;
if team_member.is_some() {
let team_members: Vec<_> = members_data
@@ -96,8 +110,12 @@ 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 {
@@ -153,17 +171,20 @@ 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::CustomAuthenticationError(
"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::CustomAuthenticationError(
"You don't have permission to edit members of this team"
.to_string(),
)
})?;
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
return Err(ApiError::CustomAuthenticationError(
"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) {
@@ -191,16 +212,23 @@ pub async fn add_team_member(
));
} else {
return Err(ApiError::InvalidInputError(
"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::InvalidInputError("An invalid User ID specified".to_string()))?;
.ok_or_else(|| {
ApiError::InvalidInputError(
"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,
@@ -232,11 +260,18 @@ pub async fn add_team_member(
"Team invite from {} to join the team for project {}",
current_user.username, result.title
),
link: format!("/{}/{}", result.project_type, ProjectId(result.id as u64)),
link: format!(
"/{}/{}",
result.project_type,
ProjectId(result.id as u64)
),
actions: vec![
NotificationActionBuilder {
title: "Accept".to_string(),
action_route: ("POST".to_string(), format!("team/{}/join", team)),
action_route: (
"POST".to_string(),
format!("team/{}/join", team),
),
},
NotificationActionBuilder {
title: "Deny".to_string(),
@@ -273,20 +308,24 @@ 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::CustomAuthenticationError(
"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::CustomAuthenticationError(
"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::CustomAuthenticationError(
"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::CustomAuthenticationError(
"You don't have permission to edit members of this team"
.to_string(),
)
})?;
let mut transaction = pool.begin().await?;
@@ -298,14 +337,16 @@ pub async fn edit_team_member(
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
return Err(ApiError::CustomAuthenticationError(
"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::InvalidInputError(
"The new permissions have permissions that you don't have".to_string(),
"The new permissions have permissions that you don't have"
.to_string(),
));
}
}
@@ -346,22 +387,34 @@ pub async fn transfer_ownership(
let id = info.into_inner().0;
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
"You don't have permission to edit members 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::InvalidInputError("The new owner specified does not exist".to_string())
})?;
let member = TeamMember::get_from_user_id(
id.into(),
current_user.id.into(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
"You don't have permission to edit members 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::InvalidInputError(
"The new owner specified does not exist".to_string(),
)
})?;
if member.role != crate::models::teams::OWNER_ROLE {
return Err(ApiError::CustomAuthenticationError(
"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(),
));
}
@@ -409,15 +462,18 @@ 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::CustomAuthenticationError(
"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::CustomAuthenticationError(
"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 {
@@ -431,7 +487,8 @@ 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, &**pool).await?;
} else {
@@ -440,7 +497,8 @@ 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
@@ -448,7 +506,8 @@ pub async fn remove_team_member(
TeamMember::delete(id, user_id, &**pool).await?;
} else {
return Err(ApiError::CustomAuthenticationError(
"You do not have permission to cancel a team invite".to_string(),
"You do not have permission to cancel a team invite"
.to_string(),
));
}
Ok(HttpResponse::NoContent().body(""))

View File

@@ -20,9 +20,11 @@ pub async fn forge_updates(
let (id,) = info.into_inner();
let project = database::models::Project::get_full_from_slug_or_project_id(&id, &**pool)
.await?
.ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?;
let project = database::models::Project::get_full_from_slug_or_project_id(
&id, &**pool,
)
.await?
.ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
@@ -38,7 +40,8 @@ pub async fn forge_updates(
)
.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?;
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
#[derive(Serialize)]
@@ -48,7 +51,11 @@ pub async fn forge_updates(
}
let mut response = ForgeUpdates {
homepage: format!("{}/mod/{}", dotenv::var("SITE_URL").unwrap_or_default(), id),
homepage: format!(
"{}/mod/{}",
dotenv::var("SITE_URL").unwrap_or_default(),
id
),
promos: HashMap::new(),
};

View File

@@ -20,8 +20,10 @@ 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(), &mut *pool.acquire().await?).await?))
Ok(HttpResponse::Ok().json(
get_user_from_headers(req.headers(), &mut *pool.acquire().await?)
.await?,
))
}
#[derive(Serialize, Deserialize)]
@@ -41,7 +43,8 @@ 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))
}
@@ -52,7 +55,8 @@ 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;
@@ -82,9 +86,11 @@ pub async fn projects_list(
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
let id_option =
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
.await?;
let id_option = crate::database::models::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();
@@ -93,17 +99,24 @@ pub async fn projects_list(
if current_user.role.is_mod() || current_user.id == user_id {
User::get_projects_private(id, &**pool).await?
} else {
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
User::get_projects(
id,
ProjectStatus::Approved.as_str(),
&**pool,
)
.await?
}
} else {
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool)
.await?
};
let response: Vec<_> = crate::database::Project::get_many_full(project_data, &**pool)
.await?
.into_iter()
.map(Project::from)
.collect();
let response: Vec<_> =
crate::database::Project::get_many_full(project_data, &**pool)
.await?
.into_iter()
.map(Project::from)
.collect();
Ok(HttpResponse::Ok().json(response))
} else {
@@ -152,13 +165,15 @@ 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::ValidationError(validation_errors_to_string(err, None)))?;
new_user.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None))
})?;
let id_option =
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
.await?;
let id_option = crate::database::models::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();
@@ -168,8 +183,10 @@ pub async fn user_edit(
if let Some(username) = &new_user.username {
let user_option =
crate::database::models::User::get_id_from_username_or_id(username, &**pool)
.await?;
crate::database::models::User::get_id_from_username_or_id(
username, &**pool,
)
.await?;
if user_option.is_none() {
sqlx::query!(
@@ -282,19 +299,23 @@ 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 = dotenv::var("CDN_URL")?;
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 =
crate::database::models::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::CustomAuthenticationError(
"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(),
));
}
@@ -322,8 +343,12 @@ 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 upload_data = file_host
.upload_file(
@@ -374,9 +399,11 @@ 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 =
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
.await?;
let id_option = crate::database::models::User::get_id_from_username_or_id(
&*info.into_inner().0,
&**pool,
)
.await?;
if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() {
@@ -389,9 +416,15 @@ pub async fn user_delete(
let result;
if &*removal_type.removal_type == "full" {
result = crate::database::models::User::remove_full(id, &mut transaction).await?;
result = crate::database::models::User::remove_full(
id,
&mut transaction,
)
.await?;
} else {
result = crate::database::models::User::remove(id, &mut transaction).await?;
result =
crate::database::models::User::remove(id, &mut transaction)
.await?;
};
transaction.commit().await?;
@@ -413,9 +446,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id(
&*info.into_inner().0,
&**pool,
)
.await?;
if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() {
@@ -441,11 +476,12 @@ 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 {
@@ -460,9 +496,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id(
&*info.into_inner().0,
&**pool,
)
.await?;
if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() {

View File

@@ -30,15 +30,18 @@ pub async fn get_mods(
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(Project::from)
.collect();
let projects: Vec<_> =
database::Project::get_many_full(project_ids, &**pool)
.await?
.into_iter()
.map(Project::from)
.collect();
Ok(HttpResponse::Ok().json(projects))
}

View File

@@ -1,6 +1,8 @@
use crate::file_hosting::FileHost;
use crate::models::projects::SearchRequest;
use crate::routes::project_creation::{project_create_inner, undo_uploads, CreateError};
use crate::routes::project_creation::{
project_create_inner, undo_uploads, CreateError,
};
use crate::routes::projects::ProjectIds;
use crate::routes::ApiError;
use crate::search::{search_for_project, SearchConfig, SearchError};
@@ -89,12 +91,14 @@ pub async fn mods_get(
ids: web::Query<ProjectIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let project_ids =
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.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();

View File

@@ -2,7 +2,9 @@ use crate::models::ids::ReportId;
use crate::models::projects::{ProjectId, VersionId};
use crate::models::users::UserId;
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::web;
use actix_web::{get, post, HttpRequest, HttpResponse};
use chrono::{DateTime, Utc};
@@ -56,24 +58,31 @@ 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::InvalidInputError("Error while parsing request payload!".to_string())
ApiError::InvalidInputError(
"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::InvalidInputError(format!("Invalid report type: {}", new_report.report_type))
ApiError::InvalidInputError(format!(
"Invalid report type: {}",
new_report.report_type
))
})?;
let mut report = crate::database::models::report_item::Report {
id,
@@ -89,17 +98,29 @@ pub async fn report_create(
match new_report.item_type {
ItemType::Mod => {
report.project_id = Some(
serde_json::from_str::<ProjectId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<ProjectId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Version => {
report.version_id = Some(
serde_json::from_str::<VersionId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<VersionId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::User => {
report.user_id = Some(
serde_json::from_str::<UserId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<UserId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Unknown => {
@@ -160,8 +181,10 @@ pub async fn reports(
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
.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();

View File

@@ -1,4 +1,6 @@
use crate::database::models::categories::{Category, GameVersion, Loader, ProjectType};
use crate::database::models::categories::{
Category, GameVersion, Loader, ProjectType,
};
use crate::routes::ApiError;
use crate::util::auth::check_is_admin_from_headers;
use actix_web::{get, put, web};
@@ -8,7 +10,9 @@ use sqlx::PgPool;
const DEFAULT_ICON: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>"#;
#[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()
@@ -28,11 +32,16 @@ pub async fn category_create(
let name = category.into_inner().0;
let project_type = crate::database::models::ProjectTypeId::get_id("mod".to_string(), &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError("Specified project type does not exist!".to_string())
})?;
let project_type = crate::database::models::ProjectTypeId::get_id(
"mod".to_string(),
&**pool,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"Specified project type does not exist!".to_string(),
)
})?;
let _id = Category::builder()
.name(&name)?
@@ -45,7 +54,9 @@ pub async fn category_create(
}
#[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 results = Loader::list(&**pool)
.await?
.into_iter()
@@ -67,12 +78,16 @@ pub async fn loader_create(
let name = loader.into_inner().0;
let mut transaction = pool.begin().await?;
let project_types = ProjectType::get_many_id(&["mod".to_string()], &mut *transaction).await?;
let project_types =
ProjectType::get_many_id(&["mod".to_string()], &mut *transaction)
.await?;
let _id = Loader::builder()
.name(&name)?
.icon(DEFAULT_ICON)?
.supported_project_types(&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>())?
.supported_project_types(
&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>(),
)?
.insert(&mut transaction)
.await?;
@@ -92,11 +107,15 @@ pub async fn game_version_list(
query: web::Query<GameVersionQueryData>,
) -> Result<HttpResponse, ApiError> {
if query.type_.is_some() || query.major.is_some() {
let results = GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool)
.await?
.into_iter()
.map(|x| x.version)
.collect::<Vec<String>>();
let results = GameVersion::list_filter(
query.type_.as_deref(),
query.major,
&**pool,
)
.await?
.into_iter()
.map(|x| x.version)
.collect::<Vec<String>>();
Ok(HttpResponse::Ok().json(results))
} else {
let results = GameVersion::list(&**pool)

View File

@@ -29,18 +29,20 @@ pub async fn team_members_get(
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0;
let members_data =
crate::database::models::TeamMember::get_from_team(id.into(), &**pool).await?;
crate::database::models::TeamMember::get_from_team(id.into(), &**pool)
.await?;
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
if let Some(user) = current_user {
let team_member = crate::database::models::TeamMember::get_from_user_id(
id.into(),
user.id.into(),
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?;
let team_member =
crate::database::models::TeamMember::get_from_user_id(
id.into(),
user.id.into(),
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?;
if team_member.is_some() {
let team_members: Vec<TeamMember> = members_data

View File

@@ -15,9 +15,11 @@ pub async fn mods_list(
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
let id_option =
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
.await?;
let id_option = crate::database::models::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();
@@ -26,10 +28,16 @@ pub async fn mods_list(
if current_user.role.is_mod() || current_user.id == user_id {
User::get_projects_private(id, &**pool).await?
} else {
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
User::get_projects(
id,
ProjectStatus::Approved.as_str(),
&**pool,
)
.await?
}
} else {
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool)
.await?
};
let response = project_data
@@ -50,9 +58,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id(
&*info.into_inner().0,
&**pool,
)
.await?;
if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() {
@@ -71,7 +81,9 @@ pub async fn user_follows(
id as crate::database::models::ids::UserId,
)
.fetch_many(&**pool)
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.mod_id as u64))) })
.try_filter_map(|e| async {
Ok(e.right().map(|m| ProjectId(m.mod_id as u64)))
})
.try_collect::<Vec<ProjectId>>()
.await?;

View File

@@ -1,9 +1,12 @@
use crate::database::models;
use crate::database::models::notification_item::NotificationBuilder;
use crate::database::models::version_item::{VersionBuilder, VersionFileBuilder};
use crate::database::models::version_item::{
VersionBuilder, VersionFileBuilder,
};
use crate::file_hosting::FileHost;
use crate::models::projects::{
Dependency, GameVersion, Loader, ProjectId, Version, VersionFile, VersionId, VersionType,
Dependency, GameVersion, Loader, ProjectId, Version, VersionFile,
VersionId, VersionType,
};
use crate::models::teams::Permissions;
use crate::routes::project_creation::{CreateError, UploadedFile};
@@ -74,8 +77,11 @@ 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;
if let Err(e) = undo_result {
@@ -103,25 +109,30 @@ 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?;
while let Some(item) = payload.next().await {
let mut field: Field = item.map_err(CreateError::MultipartError)?;
let content_disposition = field.content_disposition().clone();
let name = content_disposition
.get_name()
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
let name = content_disposition.get_name().ok_or_else(|| {
CreateError::MissingValueError("Missing content name".to_string())
})?;
if name == "data" {
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 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();
if version_create_data.project_id.is_none() {
@@ -131,10 +142,13 @@ 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,
))
})?;
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!(
@@ -162,7 +176,8 @@ async fn version_create_inner(
if results.exists.unwrap_or(true) {
return Err(CreateError::InvalidInput(
"A version with that version_number already exists".to_string(),
"A version with that version_number already exists"
.to_string(),
));
}
@@ -176,7 +191,8 @@ 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(),
)
})?;
@@ -185,11 +201,13 @@ 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!(
"
@@ -210,7 +228,9 @@ 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>>()?;
@@ -222,7 +242,9 @@ async fn version_create_inner(
all_loaders
.iter()
.find(|y| {
y.loader == x.0 && y.supported_project_types.contains(&project_type)
y.loader == x.0
&& y.supported_project_types
.contains(&project_type)
})
.ok_or_else(|| CreateError::InvalidLoader(x.0.clone()))
.map(|y| y.id)
@@ -261,7 +283,9 @@ 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!(
@@ -276,9 +300,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,
@@ -300,10 +324,12 @@ async fn version_create_inner(
.await?;
}
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(
@@ -433,8 +459,11 @@ 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;
if let Err(e) = undo_result {
@@ -477,21 +506,26 @@ async fn upload_file_to_version_inner(
}
};
let team_member =
models::TeamMember::get_from_user_id_version(version_id, user.id.into(), &mut *transaction)
.await?
.ok_or_else(|| {
CreateError::CustomAuthenticationError(
"You don't have permission to upload files to this version!".to_string(),
)
})?;
let team_member = models::TeamMember::get_from_user_id_version(
version_id,
user.id.into(),
&mut *transaction,
)
.await?
.ok_or_else(|| {
CreateError::CustomAuthenticationError(
"You don't have permission to upload files to this version!"
.to_string(),
)
})?;
if !team_member
.permissions
.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(),
));
}
@@ -510,19 +544,22 @@ 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?;
while let Some(item) = payload.next().await {
let mut field: Field = item.map_err(CreateError::MultipartError)?;
let content_disposition = field.content_disposition().clone();
let name = content_disposition
.get_name()
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
let name = content_disposition.get_name().ok_or_else(|| {
CreateError::MissingValueError("Missing content name".to_string())
})?;
if name == "data" {
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 file_data: InitialFileData = serde_json::from_slice(&data)?;
// TODO: currently no data here, but still required
@@ -532,7 +569,9 @@ 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",
))
})?;
upload_file(
@@ -596,7 +635,9 @@ pub async fn upload_file(
let (file_name, file_extension) = get_name_ext(content_disposition)?;
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, 100 * (1 << 20),
@@ -619,7 +660,8 @@ 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(),
));
}
@@ -683,9 +725,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 {

View File

@@ -128,25 +128,28 @@ pub async fn delete_file(
if let Some(row) = result {
if !user.role.is_mod() {
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::DatabaseError)?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
"You don't have permission to delete this file!".to_string(),
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::DatabaseError)?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
"You don't have permission to delete this file!"
.to_string(),
)
})?;
if !team_member
.permissions
.contains(Permissions::DELETE_VERSION)
{
return Err(ApiError::CustomAuthenticationError(
"You don't have permission to delete this file!".to_string(),
"You don't have permission to delete this file!"
.to_string(),
));
}
}
@@ -167,7 +170,8 @@ pub async fn delete_file(
if files.len() < 2 {
return Err(ApiError::InvalidInputError(
"Versions must have at least one file uploaded to them".to_string(),
"Versions must have at least one file uploaded to them"
.to_string(),
));
}
@@ -269,7 +273,9 @@ pub async fn get_update_from_hash(
.await?;
if let Some(version_id) = version_ids.last() {
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 {
@@ -436,12 +442,15 @@ pub async fn update_files(
}
}
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 response = HashMap::new();
for row in &result {
if let Some(version) = versions.iter().find(|x| x.id.0 == row.version_id) {
if let Some(version) =
versions.iter().find(|x| x.id.0 == row.version_id)
{
response.insert(
hex::encode(&row.hash),
models::projects::Version::from(version.clone()),