You've already forked AstralRinth
forked from didirus/AstralRinth
Tests 3 restructure (#754)
* moved files * moved files * initial v3 additions * moves req data * tests passing, restructuring, remove v2 * fmt; clippy; prepare * merge conflicts + issues * merge conflict, fmt, clippy, prepare * revs * fixed failing test * fixed tests
This commit is contained in:
@@ -26,6 +26,7 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
.wrap(default_cors())
|
||||
.configure(admin::config)
|
||||
.configure(analytics_get::config)
|
||||
// Todo: separate these- they need to also follow v2-v3 conversion
|
||||
.configure(crate::auth::session::config)
|
||||
.configure(crate::auth::flows::config)
|
||||
.configure(crate::auth::pats::config)
|
||||
|
||||
@@ -67,13 +67,15 @@ pub async fn project_search(
|
||||
facet
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
let version = match facet.split(':').nth(1) {
|
||||
Some(version) => version,
|
||||
let val = match facet.split(':').nth(1) {
|
||||
Some(val) => val,
|
||||
None => return facet.to_string(),
|
||||
};
|
||||
|
||||
if facet.starts_with("versions:") {
|
||||
format!("game_versions:{}", version)
|
||||
format!("game_versions:{}", val)
|
||||
} else if facet.starts_with("project_type:") {
|
||||
format!("project_types:{}", val)
|
||||
} else {
|
||||
facet.to_string()
|
||||
}
|
||||
|
||||
@@ -95,6 +95,11 @@ pub async fn version_create(
|
||||
json!(legacy_create.game_versions),
|
||||
);
|
||||
|
||||
// TODO: will be overhauled with side-types overhaul
|
||||
// TODO: if not, should default to previous version
|
||||
fields.insert("client_side".to_string(), json!("required"));
|
||||
fields.insert("server_side".to_string(), json!("optional"));
|
||||
|
||||
// TODO: Some kind of handling here to ensure project type is fine.
|
||||
// We expect the version uploaded to be of loader type modpack, but there might not be a way to check here for that.
|
||||
// After all, theoretically, they could be creating a genuine 'fabric' mod, and modpack no longer carries information on whether its a mod or modpack,
|
||||
|
||||
322
src/routes/v3/admin.rs
Normal file
322
src/routes/v3/admin.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
use crate::auth::validate::get_user_record_from_bearer_token;
|
||||
use crate::database::models::User;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::analytics::Download;
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::{PayoutStatus, RecipientStatus};
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::maxmind::MaxMindIndexer;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::SearchConfig;
|
||||
use crate::util::date::get_current_tenths_of_ms;
|
||||
use crate::util::guards::admin_key_guard;
|
||||
use crate::util::routes::read_from_payload;
|
||||
use actix_web::{patch, post, web, HttpRequest, HttpResponse};
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("admin")
|
||||
.service(count_download)
|
||||
.service(trolley_webhook)
|
||||
.service(force_reindex),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DownloadBody {
|
||||
pub url: String,
|
||||
pub project_id: ProjectId,
|
||||
pub version_name: String,
|
||||
|
||||
pub ip: String,
|
||||
pub headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
// This is an internal route, cannot be used without key
|
||||
#[patch("/_count-download", guard = "admin_key_guard")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn count_download(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
maxmind: web::Data<Arc<MaxMindIndexer>>,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
download_body: web::Json<DownloadBody>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let token = download_body
|
||||
.headers
|
||||
.iter()
|
||||
.find(|x| x.0.to_lowercase() == "authorization")
|
||||
.map(|x| &**x.1);
|
||||
|
||||
let user = get_user_record_from_bearer_token(&req, token, &**pool, &redis, &session_queue)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();
|
||||
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(&download_body.version_name)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
let (version_id, project_id) = if let Some(version) = sqlx::query!(
|
||||
"
|
||||
SELECT v.id id, v.mod_id mod_id FROM files f
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE f.url = $1
|
||||
",
|
||||
download_body.url,
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.mod_id)
|
||||
} else if let Some(version) = sqlx::query!(
|
||||
"
|
||||
SELECT id, mod_id FROM versions
|
||||
WHERE ((version_number = $1 OR id = $3) AND mod_id = $2)
|
||||
",
|
||||
download_body.version_name,
|
||||
project_id as crate::database::models::ids::ProjectId,
|
||||
id_option
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.mod_id)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Specified version does not exist!".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let url = url::Url::parse(&download_body.url)
|
||||
.map_err(|_| ApiError::InvalidInput("invalid download URL specified!".to_string()))?;
|
||||
|
||||
let ip = crate::routes::analytics::convert_to_ip_v6(&download_body.ip)
|
||||
.unwrap_or_else(|_| Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped());
|
||||
|
||||
analytics_queue.add_download(Download {
|
||||
id: Uuid::new_v4(),
|
||||
recorded: get_current_tenths_of_ms(),
|
||||
domain: url.host_str().unwrap_or_default().to_string(),
|
||||
site_path: url.path().to_string(),
|
||||
user_id: user
|
||||
.and_then(|(scopes, x)| {
|
||||
if scopes.contains(Scopes::PERFORM_ANALYTICS) {
|
||||
Some(x.id.0 as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0),
|
||||
project_id: project_id as u64,
|
||||
version_id: version_id as u64,
|
||||
ip,
|
||||
country: maxmind.query(ip).await.unwrap_or_default(),
|
||||
user_agent: download_body
|
||||
.headers
|
||||
.get("user-agent")
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
headers: download_body
|
||||
.headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|x| !crate::routes::analytics::FILTERED_HEADERS.contains(&&*x.0.to_lowercase()))
|
||||
.collect(),
|
||||
});
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TrolleyWebhook {
|
||||
model: String,
|
||||
action: String,
|
||||
body: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[post("/_trolley")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn trolley_webhook(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(signature) = req.headers().get("X-PaymentRails-Signature") {
|
||||
let payload = read_from_payload(
|
||||
&mut payload,
|
||||
1 << 20,
|
||||
"Webhook payload exceeds the maximum of 1MiB.",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut signature = signature.to_str().ok().unwrap_or_default().split(',');
|
||||
let timestamp = signature
|
||||
.next()
|
||||
.and_then(|x| x.split('=').nth(1))
|
||||
.unwrap_or_default();
|
||||
let v1 = signature
|
||||
.next()
|
||||
.and_then(|x| x.split('=').nth(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut mac: Hmac<Sha256> =
|
||||
Hmac::new_from_slice(dotenvy::var("TROLLEY_WEBHOOK_SIGNATURE")?.as_bytes())
|
||||
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
||||
mac.update(timestamp.as_bytes());
|
||||
mac.update(&payload);
|
||||
let request_signature = mac.finalize().into_bytes().encode_hex::<String>();
|
||||
|
||||
if &*request_signature == v1 {
|
||||
let webhook = serde_json::from_slice::<TrolleyWebhook>(&payload)?;
|
||||
|
||||
if webhook.model == "recipient" {
|
||||
#[derive(Deserialize)]
|
||||
struct Recipient {
|
||||
pub id: String,
|
||||
pub email: Option<String>,
|
||||
pub status: Option<RecipientStatus>,
|
||||
}
|
||||
|
||||
if let Some(body) = webhook.body.get("recipient") {
|
||||
if let Ok(recipient) = serde_json::from_value::<Recipient>(body.clone()) {
|
||||
let value = sqlx::query!(
|
||||
"SELECT id FROM users WHERE trolley_id = $1",
|
||||
recipient.id
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(user) = value {
|
||||
let user = User::get_id(
|
||||
crate::database::models::UserId(user.id),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(user) = user {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if webhook.action == "deleted" {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET trolley_account_status = NULL, trolley_id = NULL
|
||||
WHERE id = $1
|
||||
",
|
||||
user.id.0
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET email = $1, email_verified = $2, trolley_account_status = $3
|
||||
WHERE id = $4
|
||||
",
|
||||
recipient.email.clone(),
|
||||
user.email_verified && recipient.email == user.email,
|
||||
recipient.status.map(|x| x.as_str()),
|
||||
user.id.0
|
||||
)
|
||||
.execute(&mut *transaction).await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if webhook.model == "payment" {
|
||||
#[derive(Deserialize)]
|
||||
struct Payment {
|
||||
pub id: String,
|
||||
pub status: PayoutStatus,
|
||||
}
|
||||
|
||||
if let Some(body) = webhook.body.get("payment") {
|
||||
if let Ok(payment) = serde_json::from_value::<Payment>(body.clone()) {
|
||||
let value = sqlx::query!(
|
||||
"SELECT id, amount, user_id, status FROM historical_payouts WHERE payment_id = $1",
|
||||
payment.id
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(payout) = value {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if payment.status.is_failed()
|
||||
&& !PayoutStatus::from_string(&payout.status).is_failed()
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET balance = balance + $1
|
||||
WHERE id = $2
|
||||
",
|
||||
payout.amount,
|
||||
payout.user_id,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE historical_payouts
|
||||
SET status = $1
|
||||
WHERE payment_id = $2
|
||||
",
|
||||
payment.status.as_str(),
|
||||
payment.id,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
User::clear_caches(
|
||||
&[(crate::database::models::UserId(payout.user_id), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[post("/_force_reindex", guard = "admin_key_guard")]
|
||||
pub async fn force_reindex(
|
||||
pool: web::Data<PgPool>,
|
||||
config: web::Data<SearchConfig>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
use crate::search::indexing::index_projects;
|
||||
index_projects(pool.as_ref().clone(), &config).await?;
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
pub use super::ApiError;
|
||||
use crate::{auth::oauth, util::cors::default_cors};
|
||||
use crate::util::cors::default_cors;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use serde_json::json;
|
||||
|
||||
pub mod admin;
|
||||
pub mod analytics_get;
|
||||
pub mod collections;
|
||||
pub mod images;
|
||||
@@ -27,20 +28,28 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("v3")
|
||||
.wrap(default_cors())
|
||||
.configure(admin::config)
|
||||
.configure(analytics_get::config)
|
||||
// TODO: write tests that catch these
|
||||
.configure(oauth_clients::config)
|
||||
.configure(crate::auth::session::config)
|
||||
.configure(crate::auth::flows::config)
|
||||
.configure(crate::auth::pats::config)
|
||||
.configure(collections::config)
|
||||
.configure(images::config)
|
||||
.configure(moderation::config)
|
||||
.configure(notifications::config)
|
||||
.configure(organizations::config)
|
||||
.configure(project_creation::config)
|
||||
.configure(projects::config)
|
||||
.configure(reports::config)
|
||||
.configure(statistics::config)
|
||||
.configure(tags::config)
|
||||
.configure(teams::config)
|
||||
.configure(threads::config)
|
||||
.configure(users::config)
|
||||
.configure(version_file::config)
|
||||
.configure(versions::config)
|
||||
.configure(oauth::config)
|
||||
.configure(oauth_clients::config),
|
||||
.configure(versions::config),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,20 +43,19 @@ use crate::database::models::oauth_client_item::OAuthClient as DBOAuthClient;
|
||||
use crate::models::ids::OAuthClientId as ApiOAuthClientId;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_user_clients);
|
||||
cfg.service(
|
||||
scope("oauth")
|
||||
.configure(crate::auth::oauth::config)
|
||||
.service(revoke_oauth_authorization)
|
||||
.service(oauth_client_create)
|
||||
.service(oauth_client_edit)
|
||||
.service(oauth_client_delete)
|
||||
.service(get_client)
|
||||
.service(get_clients)
|
||||
.service(get_user_oauth_authorizations)
|
||||
.service(revoke_oauth_authorization),
|
||||
.service(get_user_oauth_authorizations),
|
||||
);
|
||||
}
|
||||
|
||||
#[get("user/{user_id}/oauth_apps")]
|
||||
pub async fn get_user_clients(
|
||||
req: HttpRequest,
|
||||
info: web::Path<String>,
|
||||
@@ -354,6 +353,7 @@ pub async fn revoke_oauth_authorization(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
println!("Inside revoke_oauth_authorization");
|
||||
let current_user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
|
||||
@@ -23,15 +23,17 @@ use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("organizations", web::get().to(organizations_get));
|
||||
cfg.service(
|
||||
web::scope("organization")
|
||||
.route("", web::post().to(organization_create))
|
||||
.route("{id}/projects", web::get().to(organization_projects_get))
|
||||
.route("{id}", web::get().to(organization_get))
|
||||
.route("{id}", web::patch().to(organizations_edit))
|
||||
.route("{id}", web::delete().to(organization_delete))
|
||||
.route("{id}/projects", web::post().to(organization_projects_add))
|
||||
.route(
|
||||
"{id}/projects",
|
||||
"{id}/projects/{project_id}",
|
||||
web::delete().to(organization_projects_remove),
|
||||
)
|
||||
.route("{id}/icon", web::patch().to(organization_icon_edit))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use super::version_creation::InitialVersionData;
|
||||
use super::version_creation::{try_create_version_fields, InitialVersionData};
|
||||
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models::loader_fields::{
|
||||
Loader, LoaderField, LoaderFieldEnumValue, VersionField,
|
||||
};
|
||||
use crate::database::models::loader_fields::{Loader, LoaderField, LoaderFieldEnumValue};
|
||||
use crate::database::models::thread_item::ThreadBuilder;
|
||||
use crate::database::models::{self, image_item, User};
|
||||
use crate::database::redis::RedisPool;
|
||||
@@ -37,7 +35,7 @@ use thiserror::Error;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.route("create", web::post().to(project_create));
|
||||
cfg.route("project", web::post().to(project_create));
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -884,31 +882,19 @@ async fn create_initial_version(
|
||||
})
|
||||
.collect::<Result<Vec<models::LoaderId>, CreateError>>()?;
|
||||
|
||||
let loader_fields = LoaderField::get_fields(&mut **transaction, redis).await?;
|
||||
let mut version_fields = vec![];
|
||||
let loader_fields = LoaderField::get_fields(&loaders, &mut **transaction, redis).await?;
|
||||
let mut loader_field_enum_values =
|
||||
LoaderFieldEnumValue::list_many_loader_fields(&loader_fields, &mut **transaction, redis)
|
||||
.await?;
|
||||
for (key, value) in version_data.fields.iter() {
|
||||
let loader_field = loader_fields
|
||||
.iter()
|
||||
.find(|lf| &lf.field == key)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!("Loader field '{key}' does not exist!"))
|
||||
})?;
|
||||
let enum_variants = loader_field_enum_values
|
||||
.remove(&loader_field.id)
|
||||
.unwrap_or_default();
|
||||
let vf: VersionField = VersionField::check_parse(
|
||||
version_id.into(),
|
||||
loader_field.clone(),
|
||||
value.clone(),
|
||||
enum_variants,
|
||||
)
|
||||
.map_err(CreateError::InvalidInput)?;
|
||||
version_fields.push(vf);
|
||||
}
|
||||
|
||||
let version_fields = try_create_version_fields(
|
||||
version_id,
|
||||
&version_data.fields,
|
||||
&loader_fields,
|
||||
&mut loader_field_enum_values,
|
||||
)?;
|
||||
|
||||
println!("Made it past here");
|
||||
let dependencies = version_data
|
||||
.dependencies
|
||||
.iter()
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
web::scope("project")
|
||||
.route("{id}", web::get().to(project_get))
|
||||
.route("{id}/check", web::get().to(project_get_check))
|
||||
.route("{id}", web::delete().to(project_get))
|
||||
.route("{id}", web::delete().to(project_delete))
|
||||
.route("{id}", web::patch().to(project_edit))
|
||||
.route("{id}/icon", web::patch().to(project_icon_edit))
|
||||
.route("{id}/icon", web::delete().to(delete_project_icon))
|
||||
@@ -59,7 +59,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
"members",
|
||||
web::get().to(super::teams::team_members_get_project),
|
||||
)
|
||||
.route("versions", web::get().to(super::versions::version_list))
|
||||
.route("version", web::get().to(super::versions::version_list))
|
||||
.route(
|
||||
"version/{slug}",
|
||||
web::get().to(super::versions::version_project_get),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::database::models::loader_fields::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use itertools::Itertools;
|
||||
use serde_json::Value;
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -17,7 +18,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.route("loader", web::get().to(loader_list)),
|
||||
)
|
||||
.route("games", web::get().to(games_list))
|
||||
.route("loader_fields", web::get().to(loader_fields_list))
|
||||
.route("loader_field", web::get().to(loader_fields_list))
|
||||
.route("license", web::get().to(license_list))
|
||||
.route("license/{id}", web::get().to(license_text))
|
||||
.route("donation_platform", web::get().to(donation_platform_list))
|
||||
@@ -118,14 +119,20 @@ pub async fn loader_fields_list(
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let query = query.into_inner();
|
||||
let loader_field = LoaderField::get_field(&query.loader_field, &**pool, &redis)
|
||||
let all_loader_ids = Loader::list(&**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"'{}' was not a valid loader field.",
|
||||
query.loader_field
|
||||
))
|
||||
})?;
|
||||
.into_iter()
|
||||
.map(|x| x.id)
|
||||
.collect_vec();
|
||||
let loader_field =
|
||||
LoaderField::get_field(&query.loader_field, &all_loader_ids, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"'{}' was not a valid loader field.",
|
||||
query.loader_field
|
||||
))
|
||||
})?;
|
||||
|
||||
let loader_field_enum_id = match loader_field.field_type {
|
||||
LoaderFieldType::Enum(enum_id) | LoaderFieldType::ArrayEnum(enum_id) => enum_id,
|
||||
|
||||
@@ -24,8 +24,8 @@ use sqlx::PgPool;
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("thread")
|
||||
.route("{id}", web::get().to(thread_get))
|
||||
.route("inbox", web::get().to(moderation_inbox))
|
||||
.route("{id}", web::get().to(thread_get))
|
||||
.route("{id}", web::post().to(thread_send_message))
|
||||
.route("{id}/read", web::post().to(thread_read)),
|
||||
);
|
||||
@@ -517,7 +517,6 @@ pub async fn moderation_inbox(
|
||||
Some(&[Scopes::THREAD_READ]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ids = sqlx::query!(
|
||||
"
|
||||
SELECT id
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
util::{routes::read_from_payload, validate::validation_errors_to_string},
|
||||
};
|
||||
|
||||
use super::ApiError;
|
||||
use super::{oauth_clients::get_user_clients, ApiError};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("user", web::get().to(user_auth_get));
|
||||
@@ -45,7 +45,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.route("{id}/notifications", web::get().to(user_notifications))
|
||||
.route("{id}/payouts", web::get().to(user_payouts))
|
||||
.route("{id}/payouts_fees", web::get().to(user_payouts_fees))
|
||||
.route("{id}/payouts", web::post().to(user_payouts_request)),
|
||||
.route("{id}/payouts", web::post().to(user_payouts_request))
|
||||
.route("{id}/oauth_apps", web::get().to(get_user_clients)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ use futures::stream::StreamExt;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
@@ -256,37 +256,6 @@ async fn version_create_inner(
|
||||
|
||||
let all_loaders =
|
||||
models::loader_fields::Loader::list(&mut **transaction, redis).await?;
|
||||
|
||||
let loader_fields = LoaderField::get_fields(&mut **transaction, redis).await?;
|
||||
let mut version_fields = vec![];
|
||||
let mut loader_field_enum_values = LoaderFieldEnumValue::list_many_loader_fields(
|
||||
&loader_fields,
|
||||
&mut **transaction,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
for (key, value) in version_create_data.fields.iter() {
|
||||
let loader_field = loader_fields
|
||||
.iter()
|
||||
.find(|lf| &lf.field == key)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Loader field '{key}' does not exist!"
|
||||
))
|
||||
})?;
|
||||
let enum_variants = loader_field_enum_values
|
||||
.remove(&loader_field.id)
|
||||
.unwrap_or_default();
|
||||
let vf: VersionField = VersionField::check_parse(
|
||||
version_id.into(),
|
||||
loader_field.clone(),
|
||||
value.clone(),
|
||||
enum_variants,
|
||||
)
|
||||
.map_err(CreateError::InvalidInput)?;
|
||||
version_fields.push(vf);
|
||||
}
|
||||
|
||||
let loaders = version_create_data
|
||||
.loaders
|
||||
.iter()
|
||||
@@ -299,7 +268,22 @@ async fn version_create_inner(
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
selected_loaders = Some(loaders.clone());
|
||||
let loader_ids = loaders.iter().map(|y| y.id).collect_vec();
|
||||
let loader_ids: Vec<models::LoaderId> = loaders.iter().map(|y| y.id).collect_vec();
|
||||
|
||||
let loader_fields =
|
||||
LoaderField::get_fields(&loader_ids, &mut **transaction, redis).await?;
|
||||
let mut loader_field_enum_values = LoaderFieldEnumValue::list_many_loader_fields(
|
||||
&loader_fields,
|
||||
&mut **transaction,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
let version_fields = try_create_version_fields(
|
||||
version_id,
|
||||
&version_create_data.fields,
|
||||
&loader_fields,
|
||||
&mut loader_field_enum_values,
|
||||
)?;
|
||||
|
||||
let dependencies = version_create_data
|
||||
.dependencies
|
||||
@@ -966,3 +950,50 @@ pub fn get_name_ext(
|
||||
};
|
||||
Ok((file_name, file_extension))
|
||||
}
|
||||
|
||||
// Reused functionality between project_creation and version_creation
|
||||
// Create a list of VersionFields from the fetched data, and check that all mandatory fields are present
|
||||
pub fn try_create_version_fields(
|
||||
version_id: VersionId,
|
||||
submitted_fields: &HashMap<String, serde_json::Value>,
|
||||
loader_fields: &[LoaderField],
|
||||
loader_field_enum_values: &mut HashMap<models::LoaderFieldId, Vec<LoaderFieldEnumValue>>,
|
||||
) -> Result<Vec<VersionField>, CreateError> {
|
||||
let mut version_fields = vec![];
|
||||
let mut remaining_mandatory_loader_fields = loader_fields
|
||||
.iter()
|
||||
.filter(|lf| !lf.optional)
|
||||
.map(|lf| lf.field.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
for (key, value) in submitted_fields.iter() {
|
||||
let loader_field = loader_fields
|
||||
.iter()
|
||||
.find(|lf| &lf.field == key)
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Loader field '{key}' does not exist for any loaders supplied,"
|
||||
))
|
||||
})?;
|
||||
remaining_mandatory_loader_fields.remove(&loader_field.field);
|
||||
let enum_variants = loader_field_enum_values
|
||||
.remove(&loader_field.id)
|
||||
.unwrap_or_default();
|
||||
|
||||
let vf: VersionField = VersionField::check_parse(
|
||||
version_id.into(),
|
||||
loader_field.clone(),
|
||||
value.clone(),
|
||||
enum_variants,
|
||||
)
|
||||
.map_err(CreateError::InvalidInput)?;
|
||||
version_fields.push(vf);
|
||||
}
|
||||
|
||||
if !remaining_mandatory_loader_fields.is_empty() {
|
||||
return Err(CreateError::InvalidInput(format!(
|
||||
"Missing mandatory loader fields: {}",
|
||||
remaining_mandatory_loader_fields.iter().join(", ")
|
||||
)));
|
||||
}
|
||||
Ok(version_fields)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::collections::HashMap;
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("version_file")
|
||||
.route("version_id", web::get().to(get_version_from_hash))
|
||||
.route("{version_id}", web::get().to(get_version_from_hash))
|
||||
.route("{version_id}/update", web::post().to(get_update_from_hash))
|
||||
.route("project", web::post().to(get_projects_from_hashes))
|
||||
.route("{version_id}", web::delete().to(delete_file))
|
||||
@@ -380,7 +380,7 @@ pub async fn update_files(
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FileUpdateData {
|
||||
pub hash: String,
|
||||
pub loaders: Option<Vec<String>>,
|
||||
@@ -388,7 +388,7 @@ pub struct FileUpdateData {
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ManyFileUpdateData {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
@@ -461,6 +461,7 @@ pub async fn update_individual_files(
|
||||
if let Some(loaders) = &query_file.loaders {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
}
|
||||
|
||||
if let Some(loader_fields) = &query_file.loader_fields {
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) =
|
||||
@@ -472,7 +473,6 @@ pub async fn update_individual_files(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
})
|
||||
.sorted()
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::auth::{
|
||||
filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version,
|
||||
};
|
||||
use crate::database;
|
||||
use crate::database::models::loader_fields::{LoaderField, LoaderFieldEnumValue, VersionField};
|
||||
use crate::database::models::loader_fields::{
|
||||
self, LoaderField, LoaderFieldEnumValue, VersionField,
|
||||
};
|
||||
use crate::database::models::version_item::{DependencyBuilder, LoaderVersion};
|
||||
use crate::database::models::{image_item, Organization};
|
||||
use crate::database::redis::RedisPool;
|
||||
@@ -22,6 +24,7 @@ use crate::util::img;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
@@ -384,7 +387,14 @@ pub async fn version_edit_helper(
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let loader_fields = LoaderField::get_fields(&mut *transaction, &redis)
|
||||
let all_loaders = loader_fields::Loader::list(&mut *transaction, &redis).await?;
|
||||
let loader_ids = version_item
|
||||
.loaders
|
||||
.iter()
|
||||
.filter_map(|x| all_loaders.iter().find(|y| &y.loader == x).map(|y| y.id))
|
||||
.collect_vec();
|
||||
|
||||
let loader_fields = LoaderField::get_fields(&loader_ids, &mut *transaction, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|lf| version_fields_names.contains(&lf.field))
|
||||
@@ -417,7 +427,7 @@ pub async fn version_edit_helper(
|
||||
.find(|lf| lf.field == vf_name)
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Loader field '{vf_name}' does not exist."
|
||||
"Loader field '{vf_name}' does not exist for any loaders supplied."
|
||||
))
|
||||
})?;
|
||||
let enum_variants = loader_field_enum_values
|
||||
|
||||
Reference in New Issue
Block a user