Misc v3 linear tasks (#767)

* v3_reroute 404 error

* hash change

* fixed issue with error conversion

* added new model confirmation tests
+ title name change

* renaming, fields

* owner; test changes

* clippy prepare

* fmt

* merge fixes

* clippy

* working merge

* revs

* merge fixes
This commit is contained in:
Wyatt Verchere
2023-12-01 19:15:00 -08:00
committed by GitHub
parent 2d92b08404
commit a70df067bc
119 changed files with 2897 additions and 1334 deletions

View File

@@ -43,7 +43,7 @@ pub struct CollectionCreateData {
custom(function = "crate::util::validate::validate_name")
)]
/// The title or name of the project.
pub title: String,
pub name: String,
#[validate(length(min = 3, max = 255))]
/// A short description of the collection.
pub description: String,
@@ -94,7 +94,7 @@ pub async fn collection_create(
let collection_builder_actual = collection_item::CollectionBuilder {
collection_id: collection_id.into(),
user_id: current_user.id.into(),
title: collection_create_data.title,
name: collection_create_data.name,
description: collection_create_data.description,
status: CollectionStatus::Listed,
projects: initial_project_ids
@@ -111,7 +111,7 @@ pub async fn collection_create(
let response = crate::models::collections::Collection {
id: collection_id,
user: collection_builder.user_id.into(),
title: collection_builder.title.clone(),
name: collection_builder.name.clone(),
description: collection_builder.description.clone(),
created: now,
updated: now,
@@ -187,7 +187,7 @@ pub async fn collection_get(
return Ok(HttpResponse::Ok().json(Collection::from(data)));
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Deserialize, Validate)]
@@ -196,7 +196,7 @@ pub struct EditCollection {
length(min = 3, max = 64),
custom(function = "crate::util::validate::validate_name")
)]
pub title: Option<String>,
pub name: Option<String>,
#[validate(length(min = 3, max = 256))]
pub description: Option<String>,
pub status: Option<CollectionStatus>,
@@ -239,14 +239,14 @@ pub async fn collection_edit(
let mut transaction = pool.begin().await?;
if let Some(title) = &new_collection.title {
if let Some(name) = &new_collection.name {
sqlx::query!(
"
UPDATE collections
SET title = $1
SET name = $1
WHERE (id = $2)
",
title.trim(),
name.trim(),
id as database::models::ids::CollectionId,
)
.execute(&mut *transaction)
@@ -335,7 +335,7 @@ pub async fn collection_edit(
transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -526,7 +526,7 @@ pub async fn collection_delete(
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -93,10 +93,10 @@ pub async fn notification_get(
if user.id == data.user_id.into() || user.role.is_admin() {
Ok(HttpResponse::Ok().json(Notification::from(data)))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -142,7 +142,7 @@ pub async fn notification_read(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -188,7 +188,7 @@ pub async fn notification_delete(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -92,7 +92,7 @@ pub async fn get_user_clients(
Ok(HttpResponse::Ok().json(response))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -108,7 +108,7 @@ pub async fn get_client(
if let Some(client) = clients.into_iter().next() {
Ok(HttpResponse::Ok().json(client))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -241,7 +241,7 @@ pub async fn oauth_client_delete<'a>(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -349,7 +349,7 @@ pub async fn oauth_client_edit(
Ok(HttpResponse::Ok().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -71,7 +71,7 @@ pub async fn organization_projects_get(
"
SELECT m.id FROM organizations o
INNER JOIN mods m ON m.organization_id = o.id
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.title = $2 AND $2 IS NOT NULL)
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.name = $2 AND $2 IS NOT NULL)
",
possible_organization_id.map(|x| x as i64),
info
@@ -95,7 +95,7 @@ pub struct NewOrganization {
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub title: String,
pub name: String,
#[validate(length(min = 3, max = 256))]
pub description: String,
}
@@ -124,13 +124,13 @@ pub async fn organization_create(
let mut transaction = pool.begin().await?;
// Try title
let title_organization_id_option: Option<OrganizationId> =
serde_json::from_str(&format!("\"{}\"", new_organization.title)).ok();
let name_organization_id_option: Option<OrganizationId> =
serde_json::from_str(&format!("\"{}\"", new_organization.name)).ok();
let mut organization_strings = vec![];
if let Some(title_organization_id) = title_organization_id_option {
organization_strings.push(title_organization_id.to_string());
if let Some(name_organization_id) = name_organization_id_option {
organization_strings.push(name_organization_id.to_string());
}
organization_strings.push(new_organization.title.clone());
organization_strings.push(new_organization.name.clone());
let results = Organization::get_many(&organization_strings, &mut *transaction, &redis).await?;
if !results.is_empty() {
return Err(CreateError::SlugCollision);
@@ -143,6 +143,7 @@ pub async fn organization_create(
members: vec![team_item::TeamMemberBuilder {
user_id: current_user.id.into(),
role: crate::models::teams::OWNER_ROLE.to_owned(),
is_owner: true,
permissions: ProjectPermissions::all(),
organization_permissions: Some(OrganizationPermissions::all()),
accepted: true,
@@ -155,7 +156,7 @@ pub async fn organization_create(
// Create organization
let organization = Organization {
id: organization_id,
title: new_organization.title.clone(),
name: new_organization.name.clone(),
description: new_organization.description.clone(),
team_id,
icon_url: None,
@@ -243,7 +244,7 @@ pub async fn organization_get(
let organization = models::organizations::Organization::from(data, team_members);
return Ok(HttpResponse::Ok().json(organization));
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Deserialize)]
@@ -335,7 +336,7 @@ pub struct OrganizationEdit {
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub title: Option<String>,
pub name: Option<String>,
}
pub async fn organizations_edit(
@@ -397,47 +398,47 @@ pub async fn organizations_edit(
.await?;
}
if let Some(title) = &new_organization.title {
if let Some(name) = &new_organization.name {
if !perms.contains(OrganizationPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the title of this organization!"
"You do not have the permissions to edit the name of this organization!"
.to_string(),
));
}
let title_organization_id_option: Option<u64> = parse_base62(title).ok();
if let Some(title_organization_id) = title_organization_id_option {
let name_organization_id_option: Option<u64> = parse_base62(name).ok();
if let Some(name_organization_id) = name_organization_id_option {
let results = sqlx::query!(
"
SELECT EXISTS(SELECT 1 FROM organizations WHERE id=$1)
",
title_organization_id as i64
name_organization_id as i64
)
.fetch_one(&mut *transaction)
.await?;
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput(
"Title collides with other organization's id!".to_string(),
"name collides with other organization's id!".to_string(),
));
}
}
// Make sure the new title is different from the old one
// We are able to unwrap here because the title is always set
if !title.eq(&organization_item.title.clone()) {
// Make sure the new name is different from the old one
// We are able to unwrap here because the name is always set
if !name.eq(&organization_item.name.clone()) {
let results = sqlx::query!(
"
SELECT EXISTS(SELECT 1 FROM organizations WHERE title = LOWER($1))
SELECT EXISTS(SELECT 1 FROM organizations WHERE name = LOWER($1))
",
title
name
)
.fetch_one(&mut *transaction)
.await?;
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput(
"Title collides with other organization's id!".to_string(),
"Name collides with other organization's id!".to_string(),
));
}
}
@@ -445,10 +446,10 @@ pub async fn organizations_edit(
sqlx::query!(
"
UPDATE organizations
SET title = LOWER($1)
SET name = LOWER($1)
WHERE (id = $2)
",
Some(title),
Some(name),
id as database::models::ids::OrganizationId,
)
.execute(&mut *transaction)
@@ -457,7 +458,7 @@ pub async fn organizations_edit(
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.title),
Some(organization_item.name),
&redis,
)
.await?;
@@ -470,7 +471,7 @@ pub async fn organizations_edit(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -527,19 +528,19 @@ pub async fn organization_delete(
transaction.commit().await?;
database::models::Organization::clear_cache(organization.id, Some(organization.title), &redis)
database::models::Organization::clear_cache(organization.id, Some(organization.name), &redis)
.await?;
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
#[derive(Deserialize)]
pub struct OrganizationProjectAdd {
pub project_id: String, // Also allow title/slug
pub project_id: String, // Also allow name/slug
}
pub async fn organization_projects_add(
req: HttpRequest,
@@ -596,11 +597,7 @@ pub async fn organization_projects_add(
})?;
// Require ownership of a project to add it to an organization
if !current_user.role.is_admin()
&& !project_team_member
.role
.eq(crate::models::teams::OWNER_ROLE)
{
if !current_user.role.is_admin() && !project_team_member.is_owner {
return Err(ApiError::CustomAuthentication(
"You need to be an owner of a project to add it to an organization!".to_string(),
));
@@ -824,7 +821,7 @@ pub async fn organization_icon_edit(
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.title),
Some(organization_item.name),
&redis,
)
.await?;
@@ -909,7 +906,7 @@ pub async fn delete_organization_icon(
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.title),
Some(organization_item.name),
&redis,
)
.await?;

View File

@@ -158,7 +158,7 @@ pub struct ProjectCreateData {
)]
#[serde(alias = "mod_name")]
/// The title or name of the project.
pub title: String,
pub name: String,
#[validate(
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
@@ -169,11 +169,11 @@ pub struct ProjectCreateData {
#[validate(length(min = 3, max = 255))]
#[serde(alias = "mod_description")]
/// A short description of the project.
pub description: String,
pub summary: String,
#[validate(length(max = 65536))]
#[serde(alias = "mod_body")]
/// A long description of the project, in markdown.
pub body: String,
pub description: String,
#[validate(length(max = 32))]
#[validate]
@@ -225,7 +225,7 @@ pub struct NewGalleryItem {
pub featured: bool,
#[validate(length(min = 1, max = 2048))]
/// The title of the gallery item
pub title: Option<String>,
pub name: Option<String>,
#[validate(length(min = 1, max = 2048))]
/// The description of the gallery item
pub description: Option<String>,
@@ -518,7 +518,7 @@ async fn project_create_inner(
gallery_urls.push(crate::models::projects::GalleryItem {
url: format!("{cdn_url}/{url}"),
featured: item.featured,
title: item.title.clone(),
name: item.name.clone(),
description: item.description.clone(),
created: Utc::now(),
ordering: item.ordering,
@@ -616,6 +616,7 @@ async fn project_create_inner(
members: vec![models::team_item::TeamMemberBuilder {
user_id: current_user.id.into(),
role: crate::models::teams::OWNER_ROLE.to_owned(),
is_owner: true,
// Allow all permissions for project creator, even if attached to a project
permissions: ProjectPermissions::all(),
organization_permissions: None,
@@ -679,9 +680,9 @@ async fn project_create_inner(
project_id: project_id.into(),
team_id,
organization_id: project_create_data.organization_id.map(|x| x.into()),
title: project_create_data.title,
name: project_create_data.name,
summary: project_create_data.summary,
description: project_create_data.description,
body: project_create_data.body,
icon_url: icon_data.clone().map(|x| x.0),
license_url: project_create_data.license_url,
@@ -698,7 +699,7 @@ async fn project_create_inner(
.map(|x| models::project_item::GalleryItem {
image_url: x.url.clone(),
featured: x.featured,
title: x.title.clone(),
name: x.name.clone(),
description: x.description.clone(),
created: x.created,
ordering: x.ordering,
@@ -783,12 +784,11 @@ async fn project_create_inner(
slug: project_builder.slug.clone(),
project_types,
games,
team: team_id.into(),
team_id: team_id.into(),
organization: project_create_data.organization_id,
title: project_builder.title.clone(),
name: project_builder.name.clone(),
summary: project_builder.summary.clone(),
description: project_builder.description.clone(),
body: project_builder.body.clone(),
body_url: None,
published: now,
updated: now,
approved: None,

View File

@@ -166,7 +166,7 @@ pub async fn project_get(
return Ok(HttpResponse::Ok().json(Project::from(data)));
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Serialize, Deserialize, Validate)]
@@ -175,11 +175,11 @@ pub struct EditProject {
length(min = 3, max = 64),
custom(function = "crate::util::validate::validate_name")
)]
pub title: Option<String>,
pub name: Option<String>,
#[validate(length(min = 3, max = 256))]
pub description: Option<String>,
pub summary: Option<String>,
#[validate(length(max = 65536))]
pub body: Option<String>,
pub description: Option<String>,
#[validate(length(max = 3))]
pub categories: Option<Vec<String>>,
#[validate(length(max = 256))]
@@ -272,10 +272,10 @@ pub async fn project_edit(
if let Some(perms) = permissions {
let mut transaction = pool.begin().await?;
if let Some(title) = &new_project.title {
if let Some(name) = &new_project.name {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the title of this project!"
"You do not have the permissions to edit the name of this project!"
.to_string(),
));
}
@@ -283,20 +283,20 @@ pub async fn project_edit(
sqlx::query!(
"
UPDATE mods
SET title = $1
SET name = $1
WHERE (id = $2)
",
title.trim(),
name.trim(),
id as db_ids::ProjectId,
)
.execute(&mut *transaction)
.await?;
}
if let Some(description) = &new_project.description {
if let Some(summary) = &new_project.summary {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the description of this project!"
"You do not have the permissions to edit the summary of this project!"
.to_string(),
));
}
@@ -304,10 +304,10 @@ pub async fn project_edit(
sqlx::query!(
"
UPDATE mods
SET description = $1
SET summary = $1
WHERE (id = $2)
",
description,
summary,
id as db_ids::ProjectId,
)
.execute(&mut *transaction)
@@ -664,55 +664,57 @@ pub async fn project_edit(
.await?;
}
if let Some(links) = &new_project.link_urls {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the links of this project!"
.to_string(),
));
}
if !links.is_empty() {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the links of this project!"
.to_string(),
));
}
let ids_to_delete = links
.iter()
.map(|(name, _)| name.clone())
.collect::<Vec<String>>();
// Deletes all links from hashmap- either will be deleted or be replaced
sqlx::query!(
"
DELETE FROM mods_links
WHERE joining_mod_id = $1 AND joining_platform_id IN (
SELECT id FROM link_platforms WHERE name = ANY($2)
let ids_to_delete = links
.iter()
.map(|(name, _)| name.clone())
.collect::<Vec<String>>();
// Deletes all links from hashmap- either will be deleted or be replaced
sqlx::query!(
"
DELETE FROM mods_links
WHERE joining_mod_id = $1 AND joining_platform_id IN (
SELECT id FROM link_platforms WHERE name = ANY($2)
)
",
id as db_ids::ProjectId,
&ids_to_delete
)
",
id as db_ids::ProjectId,
&ids_to_delete
)
.execute(&mut *transaction)
.await?;
.execute(&mut *transaction)
.await?;
for (platform, url) in links {
if let Some(url) = url {
let platform_id = db_models::categories::LinkPlatform::get_id(
platform,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInput(format!(
"Platform {} does not exist.",
platform.clone()
))
})?;
sqlx::query!(
"
INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)
VALUES ($1, $2, $3)
",
id as db_ids::ProjectId,
platform_id as db_ids::LinkPlatformId,
url
)
.execute(&mut *transaction)
.await?;
for (platform, url) in links {
if let Some(url) = url {
let platform_id = db_models::categories::LinkPlatform::get_id(
platform,
&mut *transaction,
)
.await?
.ok_or_else(|| {
ApiError::InvalidInput(format!(
"Platform {} does not exist.",
platform.clone()
))
})?;
sqlx::query!(
"
INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)
VALUES ($1, $2, $3)
",
id as db_ids::ProjectId,
platform_id as db_ids::LinkPlatformId,
url
)
.execute(&mut *transaction)
.await?;
}
}
}
}
@@ -763,10 +765,10 @@ pub async fn project_edit(
.await?;
}
if let Some(body) = &new_project.body {
if let Some(description) = &new_project.description {
if !perms.contains(ProjectPermissions::EDIT_BODY) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the body of this project!"
"You do not have the permissions to edit the description (body) of this project!"
.to_string(),
));
}
@@ -774,10 +776,10 @@ pub async fn project_edit(
sqlx::query!(
"
UPDATE mods
SET body = $1
SET description = $1
WHERE (id = $2)
",
body,
description,
id as db_ids::ProjectId,
)
.execute(&mut *transaction)
@@ -818,7 +820,7 @@ pub async fn project_edit(
// check new description and body for links to associated images
// if they no longer exist in the description or body, delete them
let checkable_strings: Vec<&str> = vec![&new_project.description, &new_project.body]
let checkable_strings: Vec<&str> = vec![&new_project.description, &new_project.summary]
.into_iter()
.filter_map(|x| x.as_ref().map(|y| y.as_str()))
.collect();
@@ -844,7 +846,7 @@ pub async fn project_edit(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -918,7 +920,7 @@ pub async fn project_get_check(
"id": models::ids::ProjectId::from(project.inner.id)
})))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -952,7 +954,7 @@ pub async fn dependency_list(
if let Some(project) = result {
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let dependencies =
@@ -1000,7 +1002,7 @@ pub async fn dependency_list(
Ok(HttpResponse::Ok().json(DependencyInfo { projects, versions }))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -1125,7 +1127,7 @@ pub async fn projects_edit(
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(format!(
"You do not have the permissions to bulk edit project {}!",
project.inner.title
project.inner.name
)));
}
} else if project.inner.status.is_hidden() {
@@ -1136,7 +1138,7 @@ pub async fn projects_edit(
} else {
return Err(ApiError::CustomAuthentication(format!(
"You are not a member of project {}!",
project.inner.title
project.inner.name
)));
};
}
@@ -1377,7 +1379,7 @@ pub async fn project_schedule(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -1591,7 +1593,7 @@ pub async fn delete_project_icon(
pub struct GalleryCreateQuery {
pub featured: bool,
#[validate(length(min = 1, max = 255))]
pub title: Option<String>,
pub name: Option<String>,
#[validate(length(min = 1, max = 2048))]
pub description: Option<String>,
pub ordering: Option<i64>,
@@ -1712,7 +1714,7 @@ pub async fn add_gallery_item(
let gallery_item = vec![db_models::project_item::GalleryItem {
image_url: file_url,
featured: item.featured,
title: item.title,
name: item.name,
description: item.description,
created: Utc::now(),
ordering: item.ordering.unwrap_or(0),
@@ -1749,7 +1751,7 @@ pub struct GalleryEditQuery {
with = "::serde_with::rust::double_option"
)]
#[validate(length(min = 1, max = 255))]
pub title: Option<Option<String>>,
pub name: Option<Option<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
@@ -1864,15 +1866,15 @@ pub async fn edit_gallery_item(
.execute(&mut *transaction)
.await?;
}
if let Some(title) = item.title {
if let Some(name) = item.name {
sqlx::query!(
"
UPDATE mods_gallery
SET title = $2
SET name = $2
WHERE id = $1
",
id,
title
name
)
.execute(&mut *transaction)
.await?;
@@ -2101,7 +2103,7 @@ pub async fn project_delete(
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -2133,7 +2135,7 @@ pub async fn project_follow(
let project_id: db_ids::ProjectId = result.inner.id;
if !is_authorized(&result.inner, &Some(user), &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let following = sqlx::query!(

View File

@@ -361,13 +361,13 @@ pub async fn report_get(
if let Some(report) = report {
if !user.role.is_mod() && report.reporter != user.id.into() {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let report: Report = report.into();
Ok(HttpResponse::Ok().json(report))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -401,7 +401,7 @@ pub async fn report_edit(
if let Some(report) = report {
if !user.role.is_mod() && report.reporter != user.id.into() {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let mut transaction = pool.begin().await?;
@@ -479,7 +479,7 @@ pub async fn report_edit(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -519,6 +519,6 @@ pub async fn report_delete(
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -59,7 +59,7 @@ pub async fn team_members_get_project(
.ok();
if !is_authorized(&project.inner, &current_user, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let mut members_data =
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
@@ -110,7 +110,7 @@ pub async fn team_members_get_project(
.collect();
Ok(HttpResponse::Ok().json(team_members))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -174,7 +174,7 @@ pub async fn team_members_get_organization(
Ok(HttpResponse::Ok().json(team_members))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -343,6 +343,7 @@ pub async fn join_team(
Some(true),
None,
None,
None,
&mut transaction,
)
.await?;
@@ -474,12 +475,6 @@ pub async fn add_team_member(
}
}
if new_member.role == crate::models::teams::OWNER_ROLE {
return Err(ApiError::InvalidInput(
"The `Owner` role is restricted to one person".to_string(),
));
}
if new_member.payouts_split < Decimal::ZERO || new_member.payouts_split > Decimal::from(5000) {
return Err(ApiError::InvalidInput(
"Payouts split must be between 0 and 5000!".to_string(),
@@ -510,6 +505,7 @@ pub async fn add_team_member(
team_id,
user_id: new_member.user_id.into(),
role: new_member.role.clone(),
is_owner: false, // Cannot just create an owner
permissions: new_member.permissions,
organization_permissions: new_member.organization_permissions,
accepted: false,
@@ -598,11 +594,9 @@ pub async fn edit_team_member(
let mut transaction = pool.begin().await?;
if &*edit_member_db.role == crate::models::teams::OWNER_ROLE
&& (edit_member.role.is_some() || edit_member.permissions.is_some())
{
if edit_member_db.is_owner && edit_member.permissions.is_some() {
return Err(ApiError::InvalidInput(
"The owner's permission and role of a team cannot be edited".to_string(),
"The owner's permission's in a team cannot be edited".to_string(),
));
}
@@ -683,12 +677,6 @@ pub async fn edit_team_member(
}
}
if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) {
return Err(ApiError::InvalidInput(
"The `Owner` role is restricted to one person".to_string(),
));
}
TeamMember::edit_team_member(
id,
user_id,
@@ -698,6 +686,7 @@ pub async fn edit_team_member(
None,
edit_member.payouts_split,
edit_member.ordering,
None,
&mut transaction,
)
.await?;
@@ -758,7 +747,7 @@ pub async fn transfer_ownership(
)
})?;
if member.role != crate::models::teams::OWNER_ROLE {
if !member.is_owner {
return Err(ApiError::CustomAuthentication(
"You don't have permission to edit the ownership of this team".to_string(),
));
@@ -779,15 +768,17 @@ pub async fn transfer_ownership(
let mut transaction = pool.begin().await?;
// The following are the only places new_is_owner is modified.
TeamMember::edit_team_member(
id.into(),
current_user.id.into(),
None,
None,
Some(crate::models::teams::DEFAULT_ROLE.to_string()),
None,
None,
None,
None,
Some(false),
&mut transaction,
)
.await?;
@@ -797,10 +788,11 @@ pub async fn transfer_ownership(
new_owner.user_id.into(),
Some(ProjectPermissions::all()),
Some(OrganizationPermissions::all()),
Some(crate::models::teams::OWNER_ROLE.to_string()),
None,
None,
None,
None,
Some(true),
&mut transaction,
)
.await?;
@@ -841,7 +833,7 @@ pub async fn remove_team_member(
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 {
if delete_member.is_owner {
// The owner cannot be removed from a team
return Err(ApiError::CustomAuthentication(
"The owner can't be removed from a team".to_string(),
@@ -939,6 +931,6 @@ pub async fn remove_team_member(
transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -263,7 +263,7 @@ pub async fn thread_get(
return Ok(HttpResponse::Ok().json(Thread::from(data, users, &user)));
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Deserialize)]
@@ -371,7 +371,7 @@ pub async fn thread_send_message(
if let Some(thread) = result {
if !is_authorized_thread(&thread, &user, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let mut transaction = pool.begin().await?;
@@ -499,7 +499,7 @@ pub async fn thread_send_message(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -616,6 +616,6 @@ pub async fn message_delete(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -83,7 +83,7 @@ pub async fn projects_list(
Ok(HttpResponse::Ok().json(response))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -143,7 +143,7 @@ pub async fn user_get(
let response: crate::models::users::User = data.into();
Ok(HttpResponse::Ok().json(response))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -186,7 +186,7 @@ pub async fn collections_list(
Ok(HttpResponse::Ok().json(response))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -268,7 +268,7 @@ pub async fn orgs_list(
Ok(HttpResponse::Ok().json(organizations))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -458,7 +458,7 @@ pub async fn user_edit(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -536,7 +536,7 @@ pub async fn user_icon_edit(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
} else {
Err(ApiError::InvalidInput(format!(
@@ -597,10 +597,10 @@ pub async fn user_delete(
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -655,7 +655,7 @@ pub async fn user_follows(
Ok(HttpResponse::Ok().json(projects))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -696,6 +696,6 @@ pub async fn user_notifications(
notifications.sort_by(|a, b| b.created.cmp(&a.created));
Ok(HttpResponse::Ok().json(notifications))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -421,7 +421,6 @@ async fn version_create_inner(
project_types: all_project_types,
games: all_games,
changelog: builder.changelog.clone(),
changelog_url: None,
date_published: Utc::now(),
downloads: 0,
version_type: version_data.release_channel,

View File

@@ -52,8 +52,12 @@ pub async fn get_version_from_hash(
.map(|x| x.1)
.ok();
let hash = info.into_inner().0.to_lowercase();
let algorithm = hash_query
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&[hash.clone()]));
let file = database::models::Version::get_file_from_hash(
hash_query.algorithm.clone(),
algorithm,
hash,
hash_query.version_id.map(|x| x.into()),
&**pool,
@@ -64,26 +68,36 @@ pub async fn get_version_from_hash(
let version = database::models::Version::get(file.version_id, &**pool, &redis).await?;
if let Some(version) = version {
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
Ok(HttpResponse::Ok().json(models::projects::Version::from(version)))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
#[derive(Serialize, Deserialize)]
pub struct HashQuery {
#[serde(default = "default_algorithm")]
pub algorithm: String,
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
pub version_id: Option<VersionId>,
}
pub fn default_algorithm() -> String {
// Calculates whether or not to use sha1 or sha512 based on the size of the hash
pub fn default_algorithm_from_hashes(hashes: &[String]) -> String {
// Gets first hash, optionally
let empty_string = "".into();
let hash = hashes.first().unwrap_or(&empty_string);
let hash_len = hash.len();
// Sha1 = 40 characters
// Sha512 = 128 characters
// Favour sha1 as default, unless the hash is longer or equal to 128 characters
if hash_len >= 128 {
return "sha512".into();
}
"sha1".into()
}
@@ -122,7 +136,10 @@ pub async fn get_update_from_hash(
let hash = info.into_inner().0.to_lowercase();
if let Some(file) = database::models::Version::get_file_from_hash(
hash_query.algorithm.clone(),
hash_query
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&[hash.clone()])),
hash,
hash_query.version_id.map(|x| x.into()),
&**pool,
@@ -163,7 +180,7 @@ pub async fn get_update_from_hash(
if let Some(first) = versions.last() {
if !is_authorized_version(&first.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
return Ok(HttpResponse::Ok().json(models::projects::Version::from(first)));
@@ -171,14 +188,13 @@ pub async fn get_update_from_hash(
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
// Requests above with multiple versions below
#[derive(Deserialize)]
pub struct FileHashes {
#[serde(default = "default_algorithm")]
pub algorithm: String,
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
pub hashes: Vec<String>,
}
@@ -200,8 +216,13 @@ pub async fn get_versions_from_hashes(
.map(|x| x.1)
.ok();
let algorithm = file_data
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&file_data.hashes));
let files = database::models::Version::get_files_from_hash(
file_data.algorithm.clone(),
algorithm.clone(),
&file_data.hashes,
&**pool,
&redis,
@@ -220,7 +241,7 @@ pub async fn get_versions_from_hashes(
for version in versions_data {
for file in files.iter().filter(|x| x.version_id == version.id.into()) {
if let Some(hash) = file.hashes.get(&file_data.algorithm) {
if let Some(hash) = file.hashes.get(&algorithm) {
response.insert(hash.clone(), version.clone());
}
}
@@ -247,8 +268,12 @@ pub async fn get_projects_from_hashes(
.map(|x| x.1)
.ok();
let algorithm = file_data
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&file_data.hashes));
let files = database::models::Version::get_files_from_hash(
file_data.algorithm.clone(),
algorithm.clone(),
&file_data.hashes,
&**pool,
&redis,
@@ -268,7 +293,7 @@ pub async fn get_projects_from_hashes(
for project in projects_data {
for file in files.iter().filter(|x| x.project_id == project.id.into()) {
if let Some(hash) = file.hashes.get(&file_data.algorithm) {
if let Some(hash) = file.hashes.get(&algorithm) {
response.insert(hash.clone(), project.clone());
}
}
@@ -279,8 +304,7 @@ pub async fn get_projects_from_hashes(
#[derive(Deserialize)]
pub struct ManyUpdateData {
#[serde(default = "default_algorithm")]
pub algorithm: String,
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
pub hashes: Vec<String>,
pub loaders: Option<Vec<String>>,
pub loader_fields: Option<HashMap<String, Vec<serde_json::Value>>>,
@@ -304,8 +328,12 @@ pub async fn update_files(
.map(|x| x.1)
.ok();
let algorithm = update_data
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&update_data.hashes));
let files = database::models::Version::get_files_from_hash(
update_data.algorithm.clone(),
algorithm.clone(),
&update_data.hashes,
&**pool,
&redis,
@@ -366,7 +394,7 @@ pub async fn update_files(
if let Some(version) = version {
if is_authorized_version(&version.inner, &user_option, &pool).await? {
if let Some(hash) = file.hashes.get(&update_data.algorithm) {
if let Some(hash) = file.hashes.get(&algorithm) {
response.insert(
hash.clone(),
models::projects::Version::from(version.clone()),
@@ -390,8 +418,7 @@ pub struct FileUpdateData {
#[derive(Serialize, Deserialize)]
pub struct ManyFileUpdateData {
#[serde(default = "default_algorithm")]
pub algorithm: String,
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
pub hashes: Vec<FileUpdateData>,
}
@@ -413,8 +440,17 @@ pub async fn update_individual_files(
.map(|x| x.1)
.ok();
let algorithm = update_data.algorithm.clone().unwrap_or_else(|| {
default_algorithm_from_hashes(
&update_data
.hashes
.iter()
.map(|x| x.hash.clone())
.collect::<Vec<_>>(),
)
});
let files = database::models::Version::get_files_from_hash(
update_data.algorithm.clone(),
algorithm.clone(),
&update_data
.hashes
.iter()
@@ -445,7 +481,7 @@ pub async fn update_individual_files(
for project in projects {
for file in files.iter().filter(|x| x.project_id == project.inner.id) {
if let Some(hash) = file.hashes.get(&update_data.algorithm) {
if let Some(hash) = file.hashes.get(&algorithm) {
if let Some(query_file) = update_data.hashes.iter().find(|x| &x.hash == hash) {
let version = all_versions
.iter()
@@ -514,9 +550,12 @@ pub async fn delete_file(
.1;
let hash = info.into_inner().0.to_lowercase();
let algorithm = hash_query
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&[hash.clone()]));
let file = database::models::Version::get_file_from_hash(
hash_query.algorithm.clone(),
algorithm.clone(),
hash,
hash_query.version_id.map(|x| x.into()),
&**pool,
@@ -605,7 +644,7 @@ pub async fn delete_file(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -635,8 +674,12 @@ pub async fn download_version(
.ok();
let hash = info.into_inner().0.to_lowercase();
let algorithm = hash_query
.algorithm
.clone()
.unwrap_or_else(|| default_algorithm_from_hashes(&[hash.clone()]));
let file = database::models::Version::get_file_from_hash(
hash_query.algorithm.clone(),
algorithm.clone(),
hash,
hash_query.version_id.map(|x| x.into()),
&**pool,
@@ -649,16 +692,16 @@ pub async fn download_version(
if let Some(version) = version {
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
Ok(HttpResponse::TemporaryRedirect()
.append_header(("Location", &*file.url))
.json(DownloadRedirect { url: file.url }))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}

View File

@@ -82,7 +82,7 @@ pub async fn version_project_get_helper(
if let Some(project) = result {
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let versions =
@@ -100,7 +100,7 @@ pub async fn version_project_get_helper(
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Serialize, Deserialize)]
@@ -174,7 +174,7 @@ pub async fn version_get_helper(
}
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[derive(Serialize, Deserialize, Validate, Default, Debug)]
@@ -678,7 +678,7 @@ pub async fn version_edit_helper(
))
}
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -723,7 +723,7 @@ pub async fn version_list(
if let Some(project) = result {
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let loader_field_filters = filters.loader_fields.as_ref().map(|x| {
@@ -822,7 +822,7 @@ pub async fn version_list(
Ok(HttpResponse::Ok().json(response))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -924,7 +924,7 @@ pub async fn version_schedule(
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}
@@ -1010,6 +1010,6 @@ pub async fn version_delete(
if result.is_some() {
Ok(HttpResponse::NoContent().body(""))
} else {
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
}