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

@@ -80,7 +80,7 @@ pub async fn maven_metadata(
) -> Result<HttpResponse, ApiError> {
let project_id = params.into_inner().0;
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
let user_option = get_user_from_headers(
@@ -95,7 +95,7 @@ pub async fn maven_metadata(
.ok();
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let version_names = sqlx::query!(
@@ -274,7 +274,7 @@ pub async fn version_file(
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
let user_option = get_user_from_headers(
@@ -289,15 +289,15 @@ pub async fn version_file(
.ok();
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
if file == format!("{}-{}.pom", &project_id, &vnum) {
@@ -310,7 +310,7 @@ pub async fn version_file(
group_id: "maven.modrinth".to_string(),
artifact_id: project_id,
version: vnum,
name: project.inner.title,
name: project.inner.name,
description: project.inner.description,
};
return Ok(HttpResponse::Ok()
@@ -322,7 +322,7 @@ pub async fn version_file(
.body(""));
}
Ok(HttpResponse::NotFound().body(""))
Err(ApiError::NotFound)
}
#[get("maven/modrinth/{id}/{versionnum}/{file}.sha1")]
@@ -335,7 +335,7 @@ pub async fn version_file_sha1(
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
let user_option = get_user_from_headers(
@@ -350,15 +350,15 @@ pub async fn version_file_sha1(
.ok();
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
Ok(find_file(&project_id, &vnum, &version, &file)
@@ -377,7 +377,7 @@ pub async fn version_file_sha512(
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
let user_option = get_user_from_headers(
@@ -392,15 +392,15 @@ pub async fn version_file_sha512(
.ok();
if !is_authorized(&project.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
};
if !is_authorized_version(&version.inner, &user_option, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
return Err(ApiError::NotFound);
}
Ok(find_file(&project_id, &vnum, &version, &file)

View File

@@ -123,6 +123,8 @@ pub enum ApiError {
Mail(#[from] crate::auth::email::MailError),
#[error("Error while rerouting request: {0}")]
Reroute(#[from] reqwest::Error),
#[error("Resource not found")]
NotFound,
}
impl actix_web::ResponseError for ApiError {
@@ -150,6 +152,7 @@ impl actix_web::ResponseError for ApiError {
ApiError::PasswordStrengthCheck(..) => StatusCode::BAD_REQUEST,
ApiError::Mail(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::Reroute(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::NotFound => StatusCode::NOT_FOUND,
}
}
@@ -178,6 +181,7 @@ impl actix_web::ResponseError for ApiError {
ApiError::Mail(..) => "mail_error",
ApiError::Clickhouse(..) => "clickhouse_error",
ApiError::Reroute(..) => "reroute_error",
ApiError::NotFound => "not_found",
},
description: &self.to_string(),
})

View File

@@ -0,0 +1,272 @@
use super::ApiError;
use crate::database::redis::RedisPool;
use crate::routes::{v2_reroute, v3};
use crate::{models::ids::VersionId, queue::session::AuthQueue};
use actix_web::{get, web, HttpRequest, HttpResponse};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("analytics")
.service(playtimes_get)
.service(views_get)
.service(downloads_get)
.service(revenue_get)
.service(countries_downloads_get)
.service(countries_views_get),
);
}
/// The json data to be passed to fetch analytic data
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively
/// start_date and end_date are inclusive
/// resolution_minutes is optional. This refers to the window by which we are looking (every day, every minute, etc) and defaults to 1440 (1 day)
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GetData {
// only one of project_ids or version_ids should be used
// if neither are provided, all projects the user has access to will be used
pub project_ids: Option<String>,
pub version_ids: Option<String>,
pub start_date: Option<DateTime<Utc>>, // defaults to 2 weeks ago
pub end_date: Option<DateTime<Utc>>, // defaults to now
pub resolution_minutes: Option<u32>, // defaults to 1 day. Ignored in routes that do not aggregate over a resolution (eg: /countries)
}
/// Get playtime data for a set of projects or versions
/// Data is returned as a hashmap of project/version ids to a hashmap of days to playtime data
/// eg:
/// {
/// "4N1tEhnO": {
/// "20230824": 23
/// }
///}
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
#[derive(Serialize, Deserialize, Clone)]
pub struct FetchedPlaytime {
pub time: u64,
pub total_seconds: u64,
pub loader_seconds: HashMap<String, u64>,
pub game_version_seconds: HashMap<String, u64>,
pub parent_seconds: HashMap<VersionId, u64>,
}
#[get("playtime")]
pub async fn playtimes_get(
req: HttpRequest,
clickhouse: web::Data<clickhouse::Client>,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::playtimes_get(
req,
clickhouse,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: data.version_ids,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
}
/// Get view data for a set of projects or versions
/// Data is returned as a hashmap of project/version ids to a hashmap of days to views
/// eg:
/// {
/// "4N1tEhnO": {
/// "20230824": 1090
/// }
///}
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
#[get("views")]
pub async fn views_get(
req: HttpRequest,
clickhouse: web::Data<clickhouse::Client>,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::views_get(
req,
clickhouse,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: data.version_ids,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
/// Get download data for a set of projects or versions
/// Data is returned as a hashmap of project/version ids to a hashmap of days to downloads
/// eg:
/// {
/// "4N1tEhnO": {
/// "20230824": 32
/// }
///}
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
#[get("downloads")]
pub async fn downloads_get(
req: HttpRequest,
clickhouse: web::Data<clickhouse::Client>,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::downloads_get(
req,
clickhouse,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: data.version_ids,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
/// Get payout data for a set of projects
/// Data is returned as a hashmap of project ids to a hashmap of days to amount earned per day
/// eg:
/// {
/// "4N1tEhnO": {
/// "20230824": 0.001
/// }
///}
/// ONLY project IDs can be used. Unauthorized projects will be filtered out.
#[get("revenue")]
pub async fn revenue_get(
req: HttpRequest,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::revenue_get(
req,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: None,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
/// Get country data for a set of projects or versions
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to downloads.
/// Unknown countries are labeled "".
/// This is usuable to see significant performing countries per project
/// eg:
/// {
/// "4N1tEhnO": {
/// "CAN": 22
/// }
///}
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
#[get("countries/downloads")]
pub async fn countries_downloads_get(
req: HttpRequest,
clickhouse: web::Data<clickhouse::Client>,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::countries_downloads_get(
req,
clickhouse,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: data.version_ids,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
/// Get country data for a set of projects or versions
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to views.
/// Unknown countries are labeled "".
/// This is usuable to see significant performing countries per project
/// eg:
/// {
/// "4N1tEhnO": {
/// "CAN": 56165
/// }
///}
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
#[get("countries/views")]
pub async fn countries_views_get(
req: HttpRequest,
clickhouse: web::Data<clickhouse::Client>,
data: web::Query<GetData>,
session_queue: web::Data<AuthQueue>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let data = data.into_inner();
v3::analytics_get::countries_views_get(
req,
clickhouse,
web::Query(v3::analytics_get::GetData {
project_ids: data.project_ids,
version_ids: data.version_ids,
start_date: data.start_date,
end_date: data.end_date,
resolution_minutes: data.resolution_minutes,
}),
session_queue,
pool,
redis,
)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -0,0 +1,191 @@
use crate::database::redis::RedisPool;
use crate::file_hosting::FileHost;
use crate::models::collections::CollectionStatus;
use crate::queue::session::AuthQueue;
use crate::routes::v3::project_creation::CreateError;
use crate::routes::{v3, ApiError};
use actix_web::web::Data;
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::sync::Arc;
use validator::Validate;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(collections_get);
cfg.service(collection_create);
cfg.service(
web::scope("collection")
.service(collection_get)
.service(collection_delete)
.service(collection_edit)
.service(collection_icon_edit)
.service(delete_collection_icon),
);
}
#[derive(Serialize, Deserialize, Validate, Clone)]
pub struct CollectionCreateData {
#[validate(
length(min = 3, max = 64),
custom(function = "crate::util::validate::validate_name")
)]
/// The title or name of the project.
pub title: String,
#[validate(length(min = 3, max = 255))]
/// A short description of the collection.
pub description: String,
#[validate(length(max = 32))]
#[serde(default = "Vec::new")]
/// A list of initial projects to use with the created collection
pub projects: Vec<String>,
}
#[post("collection")]
pub async fn collection_create(
req: HttpRequest,
collection_create_data: web::Json<CollectionCreateData>,
client: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, CreateError> {
let collection_create_data = collection_create_data.into_inner();
v3::collections::collection_create(
req,
web::Json(v3::collections::CollectionCreateData {
name: collection_create_data.title,
description: collection_create_data.description,
projects: collection_create_data.projects,
}),
client,
redis,
session_queue,
)
.await
}
#[derive(Serialize, Deserialize)]
pub struct CollectionIds {
pub ids: String,
}
#[get("collections")]
pub async fn collections_get(
req: HttpRequest,
web::Query(ids): web::Query<CollectionIds>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::collections::collections_get(
req,
web::Query(v3::collections::CollectionIds { ids: ids.ids }),
pool,
redis,
session_queue,
)
.await
}
#[get("{id}")]
pub async fn collection_get(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::collections::collection_get(req, info, pool, redis, session_queue).await
}
#[derive(Deserialize, Validate)]
pub struct EditCollection {
#[validate(
length(min = 3, max = 64),
custom(function = "crate::util::validate::validate_name")
)]
pub title: Option<String>,
#[validate(length(min = 3, max = 256))]
pub description: Option<String>,
pub status: Option<CollectionStatus>,
#[validate(length(max = 64))]
pub new_projects: Option<Vec<String>>,
}
#[patch("{id}")]
pub async fn collection_edit(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
new_collection: web::Json<EditCollection>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let new_collection = new_collection.into_inner();
v3::collections::collection_edit(
req,
info,
pool,
web::Json(v3::collections::EditCollection {
name: new_collection.title,
description: new_collection.description,
status: new_collection.status,
new_projects: new_collection.new_projects,
}),
redis,
session_queue,
)
.await
}
#[derive(Serialize, Deserialize)]
pub struct Extension {
pub ext: String,
}
#[patch("{id}/icon")]
#[allow(clippy::too_many_arguments)]
pub async fn collection_icon_edit(
web::Query(ext): web::Query<Extension>,
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
payload: web::Payload,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::collections::collection_icon_edit(
web::Query(v3::collections::Extension { ext: ext.ext }),
req,
info,
pool,
redis,
file_host,
payload,
session_queue,
)
.await
}
#[delete("{id}/icon")]
pub async fn delete_collection_icon(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::collections::delete_collection_icon(req, info, pool, redis, file_host, session_queue).await
}
#[delete("{id}")]
pub async fn collection_delete(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::collections::collection_delete(req, info, pool, redis, session_queue).await
}

View File

@@ -1,7 +1,7 @@
use super::ApiError;
use crate::database::redis::RedisPool;
use crate::queue::session::AuthQueue;
use crate::routes::v3;
use crate::{database::redis::RedisPool, routes::v2_reroute};
use actix_web::{get, web, HttpRequest, HttpResponse};
use serde::Deserialize;
use sqlx::PgPool;
@@ -36,4 +36,5 @@ pub async fn get_projects(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -1,6 +1,9 @@
use crate::database::redis::RedisPool;
use crate::models::ids::NotificationId;
use crate::models::notifications::Notification;
use crate::models::v2::notifications::LegacyNotification;
use crate::queue::session::AuthQueue;
use crate::routes::v2_reroute;
use crate::routes::v3;
use crate::routes::ApiError;
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
@@ -33,7 +36,7 @@ pub async fn notifications_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::notifications::notifications_get(
let resp = v3::notifications::notifications_get(
req,
web::Query(v3::notifications::NotificationIds { ids: ids.ids }),
pool,
@@ -41,6 +44,17 @@ pub async fn notifications_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error);
match v2_reroute::extract_ok_json::<Vec<Notification>>(resp?).await {
Ok(notifications) => {
let notifications: Vec<LegacyNotification> = notifications
.into_iter()
.map(LegacyNotification::from)
.collect();
Ok(HttpResponse::Ok().json(notifications))
}
Err(response) => Ok(response),
}
}
#[get("{id}")]
@@ -51,7 +65,16 @@ pub async fn notification_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::notifications::notification_get(req, info, pool, redis, session_queue).await
let response = v3::notifications::notification_get(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
match v2_reroute::extract_ok_json::<Notification>(response).await {
Ok(notification) => {
let notification = LegacyNotification::from(notification);
Ok(HttpResponse::Ok().json(notification))
}
Err(response) => Ok(response),
}
}
#[patch("{id}")]
@@ -62,7 +85,9 @@ pub async fn notification_read(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::notifications::notification_read(req, info, pool, redis, session_queue).await
v3::notifications::notification_read(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}")]
@@ -73,7 +98,9 @@ pub async fn notification_delete(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::notifications::notification_delete(req, info, pool, redis, session_queue).await
v3::notifications::notification_delete(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[patch("notifications")]
@@ -92,6 +119,7 @@ pub async fn notifications_read(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("notifications")]
@@ -110,4 +138,5 @@ pub async fn notifications_delete(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -0,0 +1,265 @@
use crate::database::redis::RedisPool;
use crate::file_hosting::FileHost;
use crate::models::projects::Project;
use crate::models::v2::projects::LegacyProject;
use crate::queue::session::AuthQueue;
use crate::routes::v3::project_creation::CreateError;
use crate::routes::{v2_reroute, v3, ApiError};
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::sync::Arc;
use validator::Validate;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(organizations_get).service(organization_create);
cfg.service(
web::scope("organization")
.service(organization_get)
.service(organizations_edit)
.service(organization_delete)
.service(organization_projects_get)
.service(organization_projects_add)
.service(organization_projects_remove)
.service(organization_icon_edit)
.service(delete_organization_icon)
.service(super::teams::team_members_get_organization),
);
}
#[derive(Deserialize, Validate)]
pub struct NewOrganization {
#[validate(
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub title: String,
#[validate(length(min = 3, max = 256))]
pub description: String,
}
#[post("organization")]
pub async fn organization_create(
req: HttpRequest,
new_organization: web::Json<NewOrganization>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, CreateError> {
let new_organization = new_organization.into_inner();
v3::organizations::organization_create(
req,
web::Json(v3::organizations::NewOrganization {
name: new_organization.title,
description: new_organization.description,
}),
pool.clone(),
redis.clone(),
session_queue,
)
.await
}
#[get("{id}")]
pub async fn organization_get(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::organization_get(req, info, pool.clone(), redis.clone(), session_queue).await
}
#[derive(Deserialize)]
pub struct OrganizationIds {
pub ids: String,
}
#[get("organizations")]
pub async fn organizations_get(
req: HttpRequest,
web::Query(ids): web::Query<OrganizationIds>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::organizations_get(
req,
web::Query(v3::organizations::OrganizationIds { ids: ids.ids }),
pool,
redis,
session_queue,
)
.await
}
#[derive(Serialize, Deserialize, Validate)]
pub struct OrganizationEdit {
#[validate(length(min = 3, max = 256))]
pub description: Option<String>,
#[validate(
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub title: Option<String>,
}
#[patch("{id}")]
pub async fn organizations_edit(
req: HttpRequest,
info: web::Path<(String,)>,
new_organization: web::Json<OrganizationEdit>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let new_organization = new_organization.into_inner();
v3::organizations::organizations_edit(
req,
info,
web::Json(v3::organizations::OrganizationEdit {
description: new_organization.description,
name: new_organization.title,
}),
pool.clone(),
redis.clone(),
session_queue,
)
.await
}
#[delete("{id}")]
pub async fn organization_delete(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::organization_delete(req, info, pool.clone(), redis.clone(), session_queue)
.await
}
#[get("{id}/projects")]
pub async fn organization_projects_get(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let response = v3::organizations::organization_projects_get(
req,
info,
pool.clone(),
redis.clone(),
session_queue,
)
.await?;
// Convert v3 projects to v2
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
Ok(HttpResponse::Ok().json(legacy_projects))
}
Err(response) => Ok(response),
}
}
#[derive(Deserialize)]
pub struct OrganizationProjectAdd {
pub project_id: String, // Also allow title/slug
}
#[post("{id}/projects")]
pub async fn organization_projects_add(
req: HttpRequest,
info: web::Path<(String,)>,
project_info: web::Json<OrganizationProjectAdd>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let project_info = project_info.into_inner();
v3::organizations::organization_projects_add(
req,
info,
web::Json(v3::organizations::OrganizationProjectAdd {
project_id: project_info.project_id,
}),
pool.clone(),
redis.clone(),
session_queue,
)
.await
}
#[delete("{organization_id}/projects/{project_id}")]
pub async fn organization_projects_remove(
req: HttpRequest,
info: web::Path<(String, String)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::organization_projects_remove(
req,
info,
pool.clone(),
redis.clone(),
session_queue,
)
.await
}
#[derive(Serialize, Deserialize)]
pub struct Extension {
pub ext: String,
}
#[patch("{id}/icon")]
#[allow(clippy::too_many_arguments)]
pub async fn organization_icon_edit(
web::Query(ext): web::Query<Extension>,
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
payload: web::Payload,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::organization_icon_edit(
web::Query(v3::organizations::Extension { ext: ext.ext }),
req,
info,
pool.clone(),
redis.clone(),
file_host,
payload,
session_queue,
)
.await
}
#[delete("{id}/icon")]
pub async fn delete_organization_icon(
req: HttpRequest,
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::organizations::delete_organization_icon(
req,
info,
pool.clone(),
redis.clone(),
file_host,
session_queue,
)
.await
}

View File

@@ -213,10 +213,10 @@ pub async fn project_create(
}
Ok(v3::project_creation::ProjectCreateData {
title: legacy_create.title,
name: legacy_create.title,
slug: legacy_create.slug,
description: legacy_create.description,
body: legacy_create.body,
summary: legacy_create.description, // Description becomes summary
description: legacy_create.body, // Body becomes description
initial_versions,
categories: legacy_create.categories,
additional_categories: legacy_create.additional_categories,

View File

@@ -102,6 +102,8 @@ pub async fn project_search(
format!("game_versions:{}", val)
} else if facet.starts_with("project_type:") {
format!("project_types:{}", val)
} else if facet.starts_with("title:") {
format!("name:{}", val)
} else {
facet.to_string()
}
@@ -143,7 +145,10 @@ pub async fn random_projects_get(
let count = v3::projects::RandomProjects { count: count.count };
let response =
v3::projects::random_projects_get(web::Query(count), pool.clone(), redis.clone()).await?;
v3::projects::random_projects_get(web::Query(count), pool.clone(), redis.clone())
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
@@ -170,7 +175,9 @@ pub async fn projects_get(
redis.clone(),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
@@ -191,10 +198,11 @@ pub async fn project_get(
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
// Convert V2 data to V3 data
// Call V3 project creation
let response =
v3::projects::project_get(req, info, pool.clone(), redis.clone(), session_queue).await?;
let response = v3::projects::project_get(req, info, pool.clone(), redis.clone(), session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Project>(response).await {
@@ -217,7 +225,10 @@ pub async fn project_get_check(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
v3::projects::project_get_check(info, pool, redis).await
v3::projects::project_get_check(info, pool, redis)
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize)]
@@ -235,7 +246,10 @@ pub async fn dependency_list(
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
// TODO: requires V2 conversion and tests, probably
v3::projects::dependency_list(req, info, pool, redis, session_queue).await
v3::projects::dependency_list(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize, Validate)]
@@ -426,9 +440,9 @@ pub async fn project_edit(
}
let new_project = v3::projects::EditProject {
title: v2_new_project.title,
description: v2_new_project.description,
body: v2_new_project.body,
name: v2_new_project.title,
summary: v2_new_project.description, // Description becomes summary
description: v2_new_project.body, // Body becomes description
categories: v2_new_project.categories,
additional_categories: v2_new_project.additional_categories,
license_url: v2_new_project.license_url,
@@ -453,7 +467,8 @@ pub async fn project_edit(
redis.clone(),
session_queue.clone(),
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// If client and server side were set, we will call
// the version setting route for each version to set the side types for each of them.
@@ -642,6 +657,7 @@ pub async fn projects_edit(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize)]
@@ -672,6 +688,7 @@ pub async fn project_icon_edit(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}/icon")]
@@ -683,7 +700,9 @@ pub async fn delete_project_icon(
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::projects::delete_project_icon(req, info, pool, redis, file_host, session_queue).await
v3::projects::delete_project_icon(req, info, pool, redis, file_host, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize, Validate)]
@@ -714,7 +733,7 @@ pub async fn add_gallery_item(
req,
web::Query(v3::projects::GalleryCreateQuery {
featured: item.featured,
title: item.title,
name: item.title,
description: item.description,
ordering: item.ordering,
}),
@@ -726,6 +745,7 @@ pub async fn add_gallery_item(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize, Validate)]
@@ -764,7 +784,7 @@ pub async fn edit_gallery_item(
web::Query(v3::projects::GalleryEditQuery {
url: item.url,
featured: item.featured,
title: item.title,
name: item.title,
description: item.description,
ordering: item.ordering,
}),
@@ -774,6 +794,7 @@ pub async fn edit_gallery_item(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize)]
@@ -801,6 +822,7 @@ pub async fn delete_gallery_item(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}")]
@@ -812,7 +834,9 @@ pub async fn project_delete(
config: web::Data<SearchConfig>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::projects::project_delete(req, info, pool, redis, config, session_queue).await
v3::projects::project_delete(req, info, pool, redis, config, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[post("{id}/follow")]
@@ -823,7 +847,9 @@ pub async fn project_follow(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::projects::project_follow(req, info, pool, redis, session_queue).await
v3::projects::project_follow(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}/follow")]
@@ -834,5 +860,7 @@ pub async fn project_unfollow(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::projects::project_unfollow(req, info, pool, redis, session_queue).await
v3::projects::project_unfollow(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -2,7 +2,7 @@ use crate::database::redis::RedisPool;
use crate::models::ids::ImageId;
use crate::models::reports::ItemType;
use crate::queue::session::AuthQueue;
use crate::routes::{v3, ApiError};
use crate::routes::{v2_reroute, v3, ApiError};
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use serde::Deserialize;
use sqlx::PgPool;
@@ -37,7 +37,9 @@ pub async fn report_create(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::reports::report_create(req, pool, body, redis, session_queue).await
v3::reports::report_create(req, pool, body, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -74,6 +76,7 @@ pub async fn reports(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -97,6 +100,7 @@ pub async fn reports_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("report/{id}")]
@@ -107,7 +111,9 @@ pub async fn report_get(
info: web::Path<(crate::models::reports::ReportId,)>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::reports::report_get(req, pool, redis, info, session_queue).await
v3::reports::report_get(req, pool, redis, info, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize, Validate)]
@@ -139,6 +145,7 @@ pub async fn report_edit(
}),
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("report/{id}")]
@@ -149,5 +156,7 @@ pub async fn report_delete(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::reports::report_delete(req, pool, info, redis, session_queue).await
v3::reports::report_delete(req, pool, info, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -1,4 +1,4 @@
use crate::routes::{v3, ApiError};
use crate::routes::{v2_reroute, v3, ApiError};
use actix_web::{get, web, HttpResponse};
use sqlx::PgPool;
@@ -8,5 +8,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
#[get("statistics")]
pub async fn get_stats(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
v3::statistics::get_stats(pool).await
v3::statistics::get_stats(pool)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -161,7 +161,9 @@ pub struct LicenseText {
#[get("license/{id}")]
pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse, ApiError> {
v3::tags::license_text(params).await
v3::tags::license_text(params)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(serde::Serialize)]
@@ -192,6 +194,7 @@ pub async fn donation_platform_list(
Err(response) => response,
},
)
.or_else(v2_reroute::flatten_404_error)
}
#[get("report_type")]
@@ -199,7 +202,9 @@ pub async fn report_type_list(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
v3::tags::report_type_list(pool, redis).await
v3::tags::report_type_list(pool, redis)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("project_type")]
@@ -207,7 +212,9 @@ pub async fn project_type_list(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
v3::tags::project_type_list(pool, redis).await
v3::tags::project_type_list(pool, redis)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("side_type")]

View File

@@ -1,8 +1,9 @@
use crate::database::redis::RedisPool;
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId};
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId, TeamMember};
use crate::models::users::UserId;
use crate::models::v2::teams::LegacyTeamMember;
use crate::queue::session::AuthQueue;
use crate::routes::{v3, ApiError};
use crate::routes::{v2_reroute, v3, ApiError};
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
@@ -34,7 +35,20 @@ pub async fn team_members_get_project(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::team_members_get_project(req, info, pool, redis, session_queue).await
let response = v3::teams::team_members_get_project(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
let members = members
.into_iter()
.map(LegacyTeamMember::from)
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}
// Returns all members of a team, but not necessarily those of a project-team's organization (unlike team_members_get_project)
@@ -46,7 +60,20 @@ pub async fn team_members_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::team_members_get(req, info, pool, redis, session_queue).await
let response = v3::teams::team_members_get(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
let members = members
.into_iter()
.map(LegacyTeamMember::from)
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}
#[derive(Serialize, Deserialize)]
@@ -62,7 +89,7 @@ pub async fn teams_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::teams_get(
let response = v3::teams::teams_get(
req,
web::Query(v3::teams::TeamIds { ids: ids.ids }),
pool,
@@ -70,6 +97,23 @@ pub async fn teams_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error);
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Vec<TeamMember>>>(response?).await {
Ok(members) => {
let members = members
.into_iter()
.map(|members| {
members
.into_iter()
.map(LegacyTeamMember::from)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}
#[post("{id}/join")]
@@ -80,7 +124,9 @@ pub async fn join_team(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::join_team(req, info, pool, redis, session_queue).await
v3::teams::join_team(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
fn default_role() -> String {
@@ -132,6 +178,7 @@ pub async fn add_team_member(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize, Clone)]
@@ -167,6 +214,7 @@ pub async fn edit_team_member(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -194,6 +242,7 @@ pub async fn transfer_ownership(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}/members/{user_id}")]
@@ -204,5 +253,7 @@ pub async fn remove_team_member(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::remove_team_member(req, info, pool, redis, session_queue).await
v3::teams::remove_team_member(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -5,7 +5,7 @@ use crate::file_hosting::FileHost;
use crate::models::ids::ThreadMessageId;
use crate::models::threads::{MessageBody, ThreadId};
use crate::queue::session::AuthQueue;
use crate::routes::{v3, ApiError};
use crate::routes::{v2_reroute, v3, ApiError};
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
use serde::Deserialize;
use sqlx::PgPool;
@@ -30,7 +30,9 @@ pub async fn thread_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::threads::thread_get(req, info, pool, redis, session_queue).await
v3::threads::thread_get(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -54,6 +56,7 @@ pub async fn threads_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -82,6 +85,7 @@ pub async fn thread_send_message(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("inbox")]
@@ -91,7 +95,9 @@ pub async fn moderation_inbox(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::threads::moderation_inbox(req, pool, redis, session_queue).await
v3::threads::moderation_inbox(req, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[post("{id}/read")]
@@ -102,7 +108,9 @@ pub async fn thread_read(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::threads::thread_read(req, info, pool, redis, session_queue).await
v3::threads::thread_read(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[delete("{id}")]
@@ -114,5 +122,7 @@ pub async fn message_delete(
session_queue: web::Data<AuthQueue>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
) -> Result<HttpResponse, ApiError> {
v3::threads::message_delete(req, info, pool, redis, session_queue, file_host).await
v3::threads::message_delete(req, info, pool, redis, session_queue, file_host)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -1,7 +1,9 @@
use crate::database::redis::RedisPool;
use crate::file_hosting::FileHost;
use crate::models::notifications::Notification;
use crate::models::projects::Project;
use crate::models::users::{Badges, Role};
use crate::models::v2::notifications::LegacyNotification;
use crate::models::v2::projects::LegacyProject;
use crate::queue::session::AuthQueue;
use crate::routes::{v2_reroute, v3, ApiError};
@@ -36,7 +38,9 @@ pub async fn user_auth_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::users::user_auth_get(req, pool, redis, session_queue).await
v3::users::user_auth_get(req, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize)]
@@ -50,7 +54,9 @@ pub async fn users_get(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
v3::users::users_get(web::Query(v3::users::UserIds { ids: ids.ids }), pool, redis).await
v3::users::users_get(web::Query(v3::users::UserIds { ids: ids.ids }), pool, redis)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("{id}")]
@@ -59,7 +65,9 @@ pub async fn user_get(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
v3::users::user_get(info, pool, redis).await
v3::users::user_get(info, pool, redis)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("{user_id}/projects")]
@@ -70,8 +78,9 @@ pub async fn projects_list(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let response =
v3::users::projects_list(req, info, pool.clone(), redis.clone(), session_queue).await?;
let response = v3::users::projects_list(req, info, pool.clone(), redis.clone(), session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert to V2 projects
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
@@ -135,6 +144,7 @@ pub async fn user_edit(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize)]
@@ -165,6 +175,7 @@ pub async fn user_icon_edit(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Deserialize)]
@@ -198,6 +209,7 @@ pub async fn user_delete(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("{id}/follows")]
@@ -208,7 +220,9 @@ pub async fn user_follows(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::users::user_follows(req, info, pool, redis, session_queue).await
v3::users::user_follows(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[get("{id}/notifications")]
@@ -219,5 +233,18 @@ pub async fn user_notifications(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::users::user_notifications(req, info, pool, redis, session_queue).await
let response = v3::users::user_notifications(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Notification>>(response).await {
Ok(notifications) => {
let legacy_notifications: Vec<LegacyNotification> = notifications
.into_iter()
.map(LegacyNotification::from)
.collect();
Ok(HttpResponse::Ok().json(legacy_notifications))
}
Err(response) => Ok(response),
}
}

View File

@@ -3,7 +3,7 @@ use crate::database::redis::RedisPool;
use crate::models::projects::{Project, Version, VersionType};
use crate::models::v2::projects::{LegacyProject, LegacyVersion};
use crate::queue::session::AuthQueue;
use crate::routes::v3::version_file::{default_algorithm, HashQuery};
use crate::routes::v3::version_file::HashQuery;
use crate::routes::{v2_reroute, v3};
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
@@ -40,10 +40,11 @@ pub async fn get_version_from_hash(
) -> Result<HttpResponse, ApiError> {
let response =
v3::version_file::get_version_from_hash(req, info, pool, redis, hash_query, session_queue)
.await;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response?).await {
match v2_reroute::extract_ok_json::<Version>(response).await {
Ok(version) => {
let v2_version = LegacyVersion::from(version);
Ok(HttpResponse::Ok().json(v2_version))
@@ -62,7 +63,9 @@ pub async fn download_version(
hash_query: web::Query<HashQuery>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::version_file::download_version(req, info, pool, redis, hash_query, session_queue).await
v3::version_file::download_version(req, info, pool, redis, hash_query, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
// under /api/v1/version_file/{hash}
@@ -75,7 +78,9 @@ pub async fn delete_file(
hash_query: web::Query<HashQuery>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::version_file::delete_file(req, info, pool, redis, hash_query, session_queue).await
v3::version_file::delete_file(req, info, pool, redis, hash_query, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}
#[derive(Serialize, Deserialize)]
@@ -119,7 +124,8 @@ pub async fn get_update_from_hash(
web::Json(update_data),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
@@ -134,8 +140,7 @@ pub async fn get_update_from_hash(
// Requests above with multiple versions below
#[derive(Deserialize)]
pub struct FileHashes {
#[serde(default = "default_algorithm")]
pub algorithm: String,
pub algorithm: Option<String>,
pub hashes: Vec<String>,
}
@@ -160,7 +165,8 @@ pub async fn get_versions_from_hashes(
web::Json(file_data),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert to V2
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {
@@ -198,7 +204,8 @@ pub async fn get_projects_from_hashes(
web::Json(file_data),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert to V2
match v2_reroute::extract_ok_json::<HashMap<String, Project>>(response).await {
@@ -230,8 +237,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 game_versions: Option<Vec<String>>,
@@ -265,7 +271,8 @@ pub async fn update_files(
let response =
v3::version_file::update_files(req, pool, redis, web::Json(update_data), session_queue)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {
@@ -293,8 +300,7 @@ pub struct FileUpdateData {
#[derive(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>,
}
@@ -338,7 +344,8 @@ pub async fn update_individual_files(
web::Json(update_data),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {

View File

@@ -73,7 +73,8 @@ pub async fn version_list(
let response =
v3::versions::version_list(req, info, web::Query(filters), pool, redis, session_queue)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Version>>(response).await {
@@ -98,8 +99,9 @@ pub async fn version_project_get(
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner();
let response =
v3::versions::version_project_get_helper(req, id, pool, redis, session_queue).await?;
let response = v3::versions::version_project_get_helper(req, id, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
Ok(version) => {
@@ -124,8 +126,9 @@ pub async fn versions_get(
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let ids = v3::versions::VersionIds { ids: ids.ids };
let response =
v3::versions::versions_get(req, web::Query(ids), pool, redis, session_queue).await?;
let response = v3::versions::versions_get(req, web::Query(ids), pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Version>>(response).await {
@@ -149,7 +152,9 @@ pub async fn version_get(
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0;
let response = v3::versions::version_get_helper(req, id, pool, redis, session_queue).await?;
let response = v3::versions::version_get_helper(req, id, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
Ok(version) => {
@@ -248,7 +253,8 @@ pub async fn version_edit(
web::Json(serde_json::to_value(new_version)?),
session_queue,
)
.await?;
.await
.or_else(v2_reroute::flatten_404_error)?;
Ok(response)
}
@@ -260,5 +266,7 @@ pub async fn version_delete(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::versions::version_delete(req, info, pool, redis, session_queue).await
v3::versions::version_delete(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)
}

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap;
use super::v3::project_creation::CreateError;
use super::ApiError;
use crate::models::v2::projects::LegacySideType;
use crate::util::actix::{generate_multipart, MultipartSegment, MultipartSegmentData};
use actix_multipart::Multipart;
@@ -14,6 +15,7 @@ pub async fn extract_ok_json<T>(response: HttpResponse) -> Result<T, HttpRespons
where
T: serde::de::DeserializeOwned,
{
// If the response is OK, parse the json and return it
if response.status() == actix_web::http::StatusCode::OK {
let failure_http_response = || {
HttpResponse::InternalServerError().json(json!({
@@ -33,6 +35,15 @@ where
}
}
// This only removes the body of 404 responses
// This should not be used on the fallback no-route-found handler
pub fn flatten_404_error(res: ApiError) -> Result<HttpResponse, ApiError> {
match res {
ApiError::NotFound => Ok(HttpResponse::NotFound().body("")),
_ => Err(res),
}
}
pub async fn alter_actix_multipart<T, U, Fut>(
mut multipart: Multipart,
mut headers: HeaderMap,

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)
}
}