You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
272
src/routes/v2/analytics_get.rs
Normal file
272
src/routes/v2/analytics_get.rs
Normal 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)
|
||||
}
|
||||
191
src/routes/v2/collections.rs
Normal file
191
src/routes/v2/collections.rs
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
265
src/routes/v2/organizations.rs
Normal file
265
src/routes/v2/organizations.rs
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ pub async fn team_members_get_project(
|
||||
.ok();
|
||||
|
||||
if !is_authorized(&project.inner, ¤t_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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user