You've already forked AstralRinth
forked from didirus/AstralRinth
Final V2 Changes (#212)
* Redo dependencies, add rejection reasons, make notifications more readable * Fix errors, add dependency route, finish PR * Fix clippy errors
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use crate::auth::get_github_user_from_token;
|
||||
use crate::database::models::{generate_state_id, User};
|
||||
use crate::models::error::ApiError;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::ids::DecodingError;
|
||||
use crate::models::users::Role;
|
||||
use crate::util::auth::get_github_user_from_token;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
||||
use actix_web::{get, HttpResponse};
|
||||
@@ -32,7 +32,7 @@ pub enum AuthorizationError {
|
||||
#[error("Invalid Authentication credentials")]
|
||||
InvalidCredentialsError,
|
||||
#[error("Authentication Error: {0}")]
|
||||
AuthenticationError(#[from] crate::auth::AuthenticationError),
|
||||
AuthenticationError(#[from] crate::util::auth::AuthenticationError),
|
||||
#[error("Error while decoding Base62")]
|
||||
DecodingError(#[from] DecodingError),
|
||||
}
|
||||
@@ -129,78 +129,82 @@ pub async fn auth_callback(
|
||||
let mut transaction = client.begin().await?;
|
||||
let state_id = parse_base62(&*info.state)?;
|
||||
|
||||
let result = sqlx::query!(
|
||||
let result_option = sqlx::query!(
|
||||
"
|
||||
SELECT url,expires FROM states
|
||||
WHERE id = $1
|
||||
",
|
||||
state_id as i64
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let now = Utc::now();
|
||||
let duration = result.expires.signed_duration_since(now);
|
||||
if let Some(result) = result_option {
|
||||
let now = Utc::now();
|
||||
let duration = result.expires.signed_duration_since(now);
|
||||
|
||||
if duration.num_seconds() < 0 {
|
||||
return Err(AuthorizationError::InvalidCredentialsError);
|
||||
}
|
||||
if duration.num_seconds() < 0 {
|
||||
return Err(AuthorizationError::InvalidCredentialsError);
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM states
|
||||
WHERE id = $1
|
||||
",
|
||||
state_id as i64
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let client_id = dotenv::var("GITHUB_CLIENT_ID")?;
|
||||
let client_secret = dotenv::var("GITHUB_CLIENT_SECRET")?;
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/login/oauth/access_token?client_id={}&client_secret={}&code={}",
|
||||
client_id, client_secret, info.code
|
||||
);
|
||||
|
||||
let token: AccessToken = reqwest::Client::new()
|
||||
.post(&url)
|
||||
.header(reqwest::header::ACCEPT, "application/json")
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
state_id as i64
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let user = get_github_user_from_token(&*token.access_token).await?;
|
||||
let client_id = dotenv::var("GITHUB_CLIENT_ID")?;
|
||||
let client_secret = dotenv::var("GITHUB_CLIENT_SECRET")?;
|
||||
|
||||
let user_result = User::get_from_github_id(user.id, &mut *transaction).await?;
|
||||
match user_result {
|
||||
Some(x) => info!("{:?}", x.id),
|
||||
None => {
|
||||
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||
let url = format!(
|
||||
"https://github.com/login/oauth/access_token?client_id={}&client_secret={}&code={}",
|
||||
client_id, client_secret, info.code
|
||||
);
|
||||
|
||||
User {
|
||||
id: user_id,
|
||||
github_id: Some(user.id as i64),
|
||||
username: user.login,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
avatar_url: Some(user.avatar_url),
|
||||
bio: user.bio,
|
||||
created: Utc::now(),
|
||||
role: Role::Developer.to_string(),
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
let token: AccessToken = reqwest::Client::new()
|
||||
.post(&url)
|
||||
.header(reqwest::header::ACCEPT, "application/json")
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
let user = get_github_user_from_token(&*token.access_token).await?;
|
||||
|
||||
let user_result = User::get_from_github_id(user.id, &mut *transaction).await?;
|
||||
match user_result {
|
||||
Some(x) => info!("{:?}", x.id),
|
||||
None => {
|
||||
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||
|
||||
User {
|
||||
id: user_id,
|
||||
github_id: Some(user.id as i64),
|
||||
username: user.login,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
avatar_url: Some(user.avatar_url),
|
||||
bio: user.bio,
|
||||
created: Utc::now(),
|
||||
role: Role::Developer.to_string(),
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let redirect_url = format!("{}?code={}", result.url, token.access_token);
|
||||
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.header("Location", &*redirect_url)
|
||||
.json(AuthorizationInit { url: redirect_url }))
|
||||
} else {
|
||||
Err(AuthorizationError::InvalidCredentialsError)
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
let redirect_url = format!("{}?code={}", result.url, token.access_token);
|
||||
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.header("Location", &*redirect_url)
|
||||
.json(AuthorizationInit { url: redirect_url }))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::models::projects::ProjectId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use sqlx::PgPool;
|
||||
use yaserde_derive::YaSerialize;
|
||||
|
||||
@@ -55,7 +55,8 @@ pub fn projects_config(cfg: &mut web::ServiceConfig) {
|
||||
.service(projects::project_follow)
|
||||
.service(projects::project_unfollow)
|
||||
.service(teams::team_members_get_project)
|
||||
.service(web::scope("{project_id}").service(versions::version_list)),
|
||||
.service(web::scope("{project_id}").service(versions::version_list))
|
||||
.service(projects::dependency_list),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,6 +120,7 @@ pub fn teams_config(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
pub fn notifications_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(notifications::notifications_get);
|
||||
cfg.service(notifications::notification_delete);
|
||||
|
||||
cfg.service(
|
||||
web::scope("notification")
|
||||
@@ -152,13 +154,13 @@ pub enum ApiError {
|
||||
#[error("Deserialization error: {0}")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[error("Authentication Error: {0}")]
|
||||
AuthenticationError(#[from] crate::auth::AuthenticationError),
|
||||
AuthenticationError(#[from] crate::util::auth::AuthenticationError),
|
||||
#[error("Authentication Error: {0}")]
|
||||
CustomAuthenticationError(String),
|
||||
#[error("Invalid Input: {0}")]
|
||||
InvalidInputError(String),
|
||||
#[error("Error while validating input: {0}")]
|
||||
ValidationError(#[from] validator::ValidationErrors),
|
||||
ValidationError(String),
|
||||
#[error("Search Error: {0}")]
|
||||
SearchError(#[from] meilisearch_sdk::errors::Error),
|
||||
#[error("Indexing Error: {0}")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::check_is_moderator_from_headers;
|
||||
use crate::database;
|
||||
use crate::models::projects::{Project, ProjectStatus};
|
||||
use crate::util::auth::check_is_moderator_from_headers;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::models::ids::NotificationId;
|
||||
use crate::models::notifications::{Notification, NotificationAction};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
@@ -70,6 +70,7 @@ pub fn convert_notification(
|
||||
Notification {
|
||||
id: notif.id.into(),
|
||||
user_id: notif.user_id.into(),
|
||||
type_: notif.notification_type,
|
||||
title: notif.title,
|
||||
text: notif.text,
|
||||
link: notif.link,
|
||||
@@ -101,7 +102,12 @@ pub async fn notification_delete(
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if data.user_id == user.id.into() || user.role.is_mod() {
|
||||
database::models::notification_item::Notification::remove(id.into(), &**pool).await?;
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
database::models::notification_item::Notification::remove(id.into(), &mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -113,3 +119,38 @@ pub async fn notification_delete(
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("notifications")]
|
||||
pub async fn notifications_delete(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let notifications_data =
|
||||
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||
|
||||
for notification in notifications_data {
|
||||
if notification.user_id == user.id.into() || user.role.is_mod() {
|
||||
notifications.push(notification.id);
|
||||
}
|
||||
}
|
||||
|
||||
database::models::notification_item::Notification::remove_many(notifications, &mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::database::models;
|
||||
use crate::file_hosting::{FileHost, FileHostingError};
|
||||
use crate::models::error::ApiError;
|
||||
@@ -8,13 +7,13 @@ use crate::models::projects::{
|
||||
use crate::models::users::UserId;
|
||||
use crate::routes::version_creation::InitialVersionData;
|
||||
use crate::search::indexing::{queue::CreationQueue, IndexingError};
|
||||
use crate::util::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use futures::stream::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::sync::Arc;
|
||||
@@ -36,7 +35,7 @@ pub enum CreateError {
|
||||
#[error("Error while parsing JSON: {0}")]
|
||||
SerDeError(#[from] serde_json::Error),
|
||||
#[error("Error while validating input: {0}")]
|
||||
ValidationError(#[from] validator::ValidationErrors),
|
||||
ValidationError(String),
|
||||
#[error("Error while uploading file")]
|
||||
FileHostingError(#[from] FileHostingError),
|
||||
#[error("Error while validating uploaded file: {0}")]
|
||||
@@ -116,10 +115,6 @@ impl actix_web::ResponseError for CreateError {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
|
||||
}
|
||||
|
||||
fn default_project_type() -> String {
|
||||
"mod".to_string()
|
||||
}
|
||||
@@ -134,7 +129,10 @@ struct ProjectCreateData {
|
||||
#[serde(default = "default_project_type")]
|
||||
/// The project type of this mod
|
||||
pub project_type: String,
|
||||
#[validate(length(min = 3, max = 64), regex = "RE_URL_SAFE")]
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
)]
|
||||
#[serde(alias = "mod_slug")]
|
||||
/// The slug of a project, used for vanity URLs
|
||||
pub slug: String,
|
||||
@@ -153,6 +151,7 @@ struct ProjectCreateData {
|
||||
pub server_side: SideType,
|
||||
|
||||
#[validate(length(max = 64))]
|
||||
#[validate]
|
||||
/// A list of initial versions to upload with the created project
|
||||
pub initial_versions: Vec<InitialVersionData>,
|
||||
#[validate(length(max = 3))]
|
||||
@@ -326,7 +325,9 @@ pub async fn project_create_inner(
|
||||
}
|
||||
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
||||
|
||||
create_data.validate()?;
|
||||
create_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let slug_project_id_option: Option<ProjectId> =
|
||||
serde_json::from_str(&*format!("\"{}\"", create_data.slug)).ok();
|
||||
@@ -498,6 +499,12 @@ pub async fn project_create_inner(
|
||||
status = ProjectStatus::Draft;
|
||||
} else {
|
||||
status = ProjectStatus::Processing;
|
||||
|
||||
if project_create_data.initial_versions.is_empty() {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
"Project submitted for review with no initial versions",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let status_id = models::StatusId::get_id(&status, &mut *transaction)
|
||||
@@ -590,6 +597,7 @@ pub async fn project_create_inner(
|
||||
published: now,
|
||||
updated: now,
|
||||
status: status.clone(),
|
||||
rejection_data: None,
|
||||
license: License {
|
||||
id: project_create_data.license_id.clone(),
|
||||
name: "".to_string(),
|
||||
@@ -622,6 +630,12 @@ pub async fn project_create_inner(
|
||||
)
|
||||
.await?;
|
||||
indexing_queue.add(index_project);
|
||||
|
||||
if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(response.clone(), webhook_url)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
@@ -643,7 +657,9 @@ async fn create_initial_version(
|
||||
)));
|
||||
}
|
||||
|
||||
version_data.validate()?;
|
||||
version_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
|
||||
// Randomly generate a new id to be used for the version
|
||||
let version_id: VersionId = models::generate_version_id(transaction).await?.into();
|
||||
@@ -684,7 +700,11 @@ async fn create_initial_version(
|
||||
let dependencies = version_data
|
||||
.dependencies
|
||||
.iter()
|
||||
.map(|x| ((x.version_id).into(), x.dependency_type.to_string()))
|
||||
.map(|d| models::version_item::DependencyBuilder {
|
||||
version_id: d.version_id.map(|x| x.into()),
|
||||
project_id: d.project_id.map(|x| x.into()),
|
||||
dependency_type: d.dependency_type.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let version = models::version_item::VersionBuilder {
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::cache::project_cache::remove_cache_project;
|
||||
use crate::database::cache::query_project_cache::remove_cache_query_project;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{
|
||||
DonationLink, License, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||
DonationLink, License, ProjectId, ProjectStatus, RejectionReason, SearchRequest, SideType,
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::indexing::queue::CreationQueue;
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use futures::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
@@ -91,7 +93,8 @@ pub async fn project_get(
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_data =
|
||||
database::models::Project::get_full_from_slug_or_project_id(string, &**pool).await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?;
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
@@ -129,6 +132,94 @@ pub async fn project_get(
|
||||
}
|
||||
}
|
||||
|
||||
struct DependencyInfo {
|
||||
pub project: Option<models::projects::Project>,
|
||||
pub version: Option<models::projects::Version>,
|
||||
}
|
||||
|
||||
#[get("dependencies")]
|
||||
pub async fn dependency_list(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get_from_slug_or_project_id(string, &**pool).await?;
|
||||
|
||||
if let Some(project) = result {
|
||||
let id = project.id;
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
let dependencies = sqlx::query!(
|
||||
"
|
||||
SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id
|
||||
FROM versions v
|
||||
INNER JOIN dependencies d ON d.dependent_id = v.id
|
||||
WHERE v.mod_id = $1
|
||||
",
|
||||
id as database::models::ProjectId
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|x| {
|
||||
(
|
||||
database::models::VersionId(x.dependent_id),
|
||||
x.dependency_id.map(database::models::VersionId),
|
||||
x.mod_dependency_id.map(database::models::ProjectId),
|
||||
)
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<(
|
||||
database::models::VersionId,
|
||||
Option<database::models::VersionId>,
|
||||
Option<database::models::ProjectId>,
|
||||
)>>()
|
||||
.await?;
|
||||
|
||||
let projects = database::Project::get_many_full(
|
||||
dependencies.iter().map(|x| x.2).flatten().collect(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let versions = database::Version::get_many_full(
|
||||
dependencies.iter().map(|x| x.1).flatten().collect(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut response: HashMap<models::projects::VersionId, DependencyInfo> = HashMap::new();
|
||||
|
||||
for dependency in dependencies {
|
||||
response.insert(
|
||||
dependency.0.into(),
|
||||
DependencyInfo {
|
||||
project: if let Some(id) = dependency.2 {
|
||||
projects
|
||||
.iter()
|
||||
.find(|x| x.inner.id == id)
|
||||
.map(|x| convert_project(x.clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
version: if let Some(id) = dependency.1 {
|
||||
versions
|
||||
.iter()
|
||||
.find(|x| x.id == id)
|
||||
.map(|x| super::versions::convert_version(x.clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_project(
|
||||
data: database::models::project_item::QueryProject,
|
||||
) -> models::projects::Project {
|
||||
@@ -146,6 +237,14 @@ pub fn convert_project(
|
||||
published: m.published,
|
||||
updated: m.updated,
|
||||
status: data.status,
|
||||
rejection_data: if let Some(reason) = m.rejection_reason {
|
||||
Some(RejectionReason {
|
||||
reason,
|
||||
body: m.rejection_body,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
license: License {
|
||||
id: data.license_id,
|
||||
name: data.license_name,
|
||||
@@ -175,10 +274,6 @@ pub fn convert_project(
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
|
||||
}
|
||||
|
||||
/// A project returned from the API
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
pub struct EditProject {
|
||||
@@ -188,7 +283,6 @@ pub struct EditProject {
|
||||
pub description: Option<String>,
|
||||
#[validate(length(max = 65536))]
|
||||
pub body: Option<String>,
|
||||
pub status: Option<ProjectStatus>,
|
||||
#[validate(length(max = 3))]
|
||||
pub categories: Option<Vec<String>>,
|
||||
#[serde(
|
||||
@@ -236,8 +330,26 @@ pub struct EditProject {
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
#[validate(length(min = 3, max = 64), regex = "RE_URL_SAFE")]
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
)]
|
||||
pub slug: Option<Option<String>>,
|
||||
pub status: Option<ProjectStatus>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
#[validate(length(max = 2000))]
|
||||
pub rejection_reason: Option<Option<String>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
#[validate(length(max = 65536))]
|
||||
pub rejection_body: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
@@ -251,11 +363,14 @@ pub async fn project_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_project.validate()?;
|
||||
new_project
|
||||
.validate()
|
||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result =
|
||||
database::models::Project::get_full_from_slug_or_project_id(string, &**pool).await?;
|
||||
database::models::Project::get_full_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(project_item) = result {
|
||||
let id = project_item.inner.id;
|
||||
@@ -337,6 +452,12 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
if status == &ProjectStatus::Processing && project_item.versions.is_empty() {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Project submitted for review with no initial versions",
|
||||
)));
|
||||
}
|
||||
|
||||
let status_id = database::models::StatusId::get_id(&status, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
@@ -357,6 +478,30 @@ pub async fn project_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if project_item.status == ProjectStatus::Processing {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET rejection_reason = NULL
|
||||
WHERE (id = $1)
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET rejection_body = NULL
|
||||
WHERE (id = $1)
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if project_item.status.is_searchable() && !status.is_searchable() {
|
||||
delete_from_index(id.into(), config).await?;
|
||||
} else if !project_item.status.is_searchable() && status.is_searchable() {
|
||||
@@ -365,6 +510,15 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
|
||||
indexing_queue.add(index_project);
|
||||
|
||||
if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
convert_project(project_item.clone()),
|
||||
webhook_url,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,6 +838,48 @@ pub async fn project_edit(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rejection_reason) = &new_project.rejection_reason {
|
||||
if !user.role.is_mod() {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You do not have the permissions to edit the rejection reason of this project!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET rejection_reason = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
rejection_reason.as_deref(),
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(rejection_body) = &new_project.rejection_body {
|
||||
if !user.role.is_mod() {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You do not have the permissions to edit the rejection body of this project!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET rejection_body = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
rejection_body.as_deref(),
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(body) = &new_project.body {
|
||||
if !perms.contains(Permissions::EDIT_BODY) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
@@ -705,6 +901,9 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
remove_cache_project(string.clone()).await;
|
||||
remove_cache_query_project(string).await;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -736,11 +935,12 @@ pub async fn project_icon_edit(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item = database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
let project_item =
|
||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -782,12 +982,14 @@ pub async fn project_icon_edit(
|
||||
)));
|
||||
}
|
||||
|
||||
let hash = sha1::Sha1::from(bytes.clone()).hexdigest();
|
||||
|
||||
let project_id: ProjectId = project_item.id.into();
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(
|
||||
content_type,
|
||||
&format!("data/{}/icon.{}", project_id, ext.ext),
|
||||
&format!("data/{}/{}.{}", project_id, hash, ext.ext),
|
||||
bytes.to_vec(),
|
||||
)
|
||||
.await?;
|
||||
@@ -804,6 +1006,9 @@ pub async fn project_icon_edit(
|
||||
.execute(&**pool)
|
||||
.await?;
|
||||
|
||||
remove_cache_project(string.clone()).await;
|
||||
remove_cache_query_project(string).await;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInputError(format!(
|
||||
@@ -823,7 +1028,7 @@ pub async fn project_delete(
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project = database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
||||
let project = database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
@@ -851,7 +1056,14 @@ pub async fn project_delete(
|
||||
}
|
||||
}
|
||||
|
||||
let result = database::models::Project::remove_full(project.id, &**pool).await?;
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result = database::models::Project::remove_full(project.id, &mut transaction).await?;
|
||||
|
||||
remove_cache_project(string.clone()).await;
|
||||
remove_cache_query_project(string).await;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
delete_from_index(project.id.into(), config).await?;
|
||||
|
||||
@@ -893,6 +1105,8 @@ pub async fn project_follow(
|
||||
.unwrap_or(false);
|
||||
|
||||
if !following {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -901,7 +1115,7 @@ pub async fn project_follow(
|
||||
",
|
||||
project_id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&**pool)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -912,9 +1126,11 @@ pub async fn project_follow(
|
||||
user_id as database::models::ids::UserId,
|
||||
project_id as database::models::ids::ProjectId
|
||||
)
|
||||
.execute(&**pool)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInputError(
|
||||
@@ -954,6 +1170,8 @@ pub async fn project_unfollow(
|
||||
.unwrap_or(false);
|
||||
|
||||
if following {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -962,7 +1180,7 @@ pub async fn project_unfollow(
|
||||
",
|
||||
project_id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&**pool)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -973,9 +1191,11 @@ pub async fn project_unfollow(
|
||||
user_id as database::models::ids::UserId,
|
||||
project_id as database::models::ids::ProjectId
|
||||
)
|
||||
.execute(&**pool)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInputError(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use crate::models::ids::{ProjectId, UserId, VersionId};
|
||||
use crate::models::reports::{ItemType, Report};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use futures::StreamExt;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::check_is_admin_from_headers;
|
||||
use crate::database::models;
|
||||
use crate::database::models::categories::{DonationPlatform, License, ProjectType, ReportType};
|
||||
use crate::util::auth::check_is_admin_from_headers;
|
||||
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
||||
use models::categories::{Category, GameVersion, Loader};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::notification_item::{NotificationActionBuilder, NotificationBuilder};
|
||||
use crate::database::models::team_item::QueryTeamMember;
|
||||
use crate::database::models::TeamMember;
|
||||
@@ -6,6 +5,7 @@ use crate::models::ids::ProjectId;
|
||||
use crate::models::teams::{Permissions, TeamId};
|
||||
use crate::models::users::UserId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
@@ -246,6 +246,7 @@ pub async fn add_team_member(
|
||||
|
||||
let team: TeamId = team_id.into();
|
||||
NotificationBuilder {
|
||||
notification_type: Some("team_invite".to_string()),
|
||||
title: "You have been invited to join a team!".to_string(),
|
||||
text: format!(
|
||||
"Team invite from {} to join the team for project {}",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::User;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::notifications::Notification;
|
||||
@@ -6,6 +5,8 @@ use crate::models::projects::{Project, ProjectStatus};
|
||||
use crate::models::users::{Role, UserId};
|
||||
use crate::routes::notifications::convert_notification;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||
use futures::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -166,7 +167,9 @@ pub async fn user_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_user.validate()?;
|
||||
new_user
|
||||
.validate()
|
||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let id_option =
|
||||
crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool)
|
||||
@@ -396,13 +399,17 @@ pub async fn user_delete(
|
||||
));
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result;
|
||||
if &*removal_type.removal_type == "full" {
|
||||
result = crate::database::models::User::remove_full(id, &**pool).await?;
|
||||
result = crate::database::models::User::remove_full(id, &mut transaction).await?;
|
||||
} else {
|
||||
result = crate::database::models::User::remove(id, &**pool).await?;
|
||||
result = crate::database::models::User::remove(id, &mut transaction).await?;
|
||||
};
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::auth::check_is_moderator_from_headers;
|
||||
use crate::database;
|
||||
use crate::models::projects::{Project, ProjectStatus};
|
||||
use crate::routes::moderation::ResultCount;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::check_is_moderator_from_headers;
|
||||
use actix_web::web;
|
||||
use actix_web::{get, HttpRequest, HttpResponse};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::projects::SearchRequest;
|
||||
use crate::routes::project_creation::{project_create_inner, undo_uploads, CreateError};
|
||||
@@ -6,6 +5,7 @@ use crate::routes::projects::{convert_project, ProjectIds};
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::indexing::queue::CreationQueue;
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::{database, models};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::web;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use crate::models::ids::ReportId;
|
||||
use crate::models::projects::{ProjectId, VersionId};
|
||||
use crate::models::users::UserId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use actix_web::web;
|
||||
use actix_web::{get, post, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::auth::check_is_admin_from_headers;
|
||||
use crate::database::models::categories::{Category, GameVersion, Loader, ProjectType};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::check_is_admin_from_headers;
|
||||
use actix_web::{get, put, web};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::models::teams::{Permissions, TeamId};
|
||||
use crate::models::users::UserId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::User;
|
||||
use crate::models::ids::UserId;
|
||||
use crate::models::projects::{ProjectId, ProjectStatus};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::web;
|
||||
use actix_web::{get, HttpRequest, HttpResponse};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::{ProjectId, UserId, VersionId};
|
||||
use crate::models::projects::{Dependency, GameVersion, Loader, Version, VersionFile, VersionType};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::versions::{convert_version, VersionIds, VersionListFilters};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::{database, models, Pepper};
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::version_item::{VersionBuilder, VersionFileBuilder};
|
||||
@@ -8,28 +7,27 @@ use crate::models::projects::{
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::project_creation::{CreateError, UploadedFile};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use crate::validate::{validate_file, ValidationResult};
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use futures::stream::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use validator::Validate;
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_\-.]*$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||
pub struct InitialVersionData {
|
||||
#[serde(alias = "mod_id")]
|
||||
pub project_id: Option<ProjectId>,
|
||||
#[validate(length(min = 1, max = 256))]
|
||||
pub file_parts: Vec<String>,
|
||||
#[validate(length(min = 1, max = 64), regex = "RE_URL_SAFE")]
|
||||
#[validate(
|
||||
length(min = 1, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
)]
|
||||
pub version_number: String,
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub version_title: String,
|
||||
@@ -127,7 +125,9 @@ async fn version_create_inner(
|
||||
));
|
||||
}
|
||||
|
||||
version_create_data.validate()?;
|
||||
version_create_data.validate().map_err(|err| {
|
||||
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let project_id: models::ProjectId = version_create_data.project_id.unwrap().into();
|
||||
|
||||
@@ -234,7 +234,11 @@ async fn version_create_inner(
|
||||
let dependencies = version_create_data
|
||||
.dependencies
|
||||
.iter()
|
||||
.map(|x| ((x.version_id).into(), x.dependency_type.to_string()))
|
||||
.map(|d| models::version_item::DependencyBuilder {
|
||||
version_id: d.version_id.map(|x| x.into()),
|
||||
project_id: d.project_id.map(|x| x.into()),
|
||||
dependency_type: d.dependency_type.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
version_builder = Some(VersionBuilder {
|
||||
@@ -332,9 +336,10 @@ async fn version_create_inner(
|
||||
let version_id: VersionId = builder.version_id.into();
|
||||
|
||||
NotificationBuilder {
|
||||
title: "A project you followed has been updated!".to_string(),
|
||||
notification_type: Some("project_update".to_string()),
|
||||
title: format!("**{}** has been updated!", result.title),
|
||||
text: format!(
|
||||
"Project {} has been updated to version {}",
|
||||
"The project, {}, has released a new version: {}",
|
||||
result.title,
|
||||
version_data.version_number.clone()
|
||||
),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{GameVersion, Loader};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::{database, Pepper};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -118,7 +118,19 @@ async fn download_version_inner(
|
||||
pepper: &web::Data<Pepper>,
|
||||
) -> Result<(), ApiError> {
|
||||
let real_ip = req.connection_info();
|
||||
let ip_option = real_ip.borrow().remote_addr();
|
||||
let ip_option = if dotenv::var("CLOUDFLARE_INTEGRATION")
|
||||
.ok()
|
||||
.map(|i| i.parse().unwrap())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||
header.to_str().ok()
|
||||
} else {
|
||||
real_ip.borrow().remote_addr()
|
||||
}
|
||||
} else {
|
||||
real_ip.borrow().remote_addr()
|
||||
};
|
||||
|
||||
if let Some(ip) = ip_option {
|
||||
let hash = sha1::Sha1::from(format!("{}{}", ip, pepper.pepper)).hexdigest();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use super::ApiError;
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::projects::{Dependency, DependencyType};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
@@ -189,8 +188,9 @@ pub fn convert_version(
|
||||
.dependencies
|
||||
.into_iter()
|
||||
.map(|d| Dependency {
|
||||
version_id: d.0.into(),
|
||||
dependency_type: DependencyType::from_str(&*d.1),
|
||||
version_id: d.version_id.map(|x| x.into()),
|
||||
project_id: d.project_id.map(|x| x.into()),
|
||||
dependency_type: DependencyType::from_str(&*d.dependency_type),
|
||||
})
|
||||
.collect(),
|
||||
game_versions: data
|
||||
@@ -206,15 +206,14 @@ pub fn convert_version(
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
pub struct EditVersion {
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub name: Option<String>,
|
||||
#[validate(length(min = 1, max = 64), regex = "RE_URL_SAFE")]
|
||||
#[validate(
|
||||
length(min = 1, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
)]
|
||||
pub version_number: Option<String>,
|
||||
#[validate(length(max = 65536))]
|
||||
pub changelog: Option<String>,
|
||||
@@ -236,7 +235,9 @@ pub async fn version_edit(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
new_version.validate()?;
|
||||
new_version
|
||||
.validate()
|
||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
|
||||
let version_id = info.into_inner().0;
|
||||
let id = version_id.into();
|
||||
@@ -332,21 +333,17 @@ pub async fn version_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
for dependency in dependencies {
|
||||
let dependency_id: database::models::ids::VersionId =
|
||||
dependency.version_id.clone().into();
|
||||
let builders = dependencies
|
||||
.iter()
|
||||
.map(|x| database::models::version_item::DependencyBuilder {
|
||||
project_id: x.project_id.clone().map(|x| x.into()),
|
||||
version_id: x.version_id.clone().map(|x| x.into()),
|
||||
dependency_type: x.dependency_type.to_string(),
|
||||
})
|
||||
.collect::<Vec<database::models::version_item::DependencyBuilder>>();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO dependencies (dependent_id, dependency_id, dependency_type)
|
||||
VALUES ($1, $2, $3)
|
||||
",
|
||||
id as database::models::ids::VersionId,
|
||||
dependency_id as database::models::ids::VersionId,
|
||||
dependency.dependency_type.as_str()
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
for dependency in builders {
|
||||
dependency.insert(version_item.id, &mut transaction).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +530,11 @@ pub async fn version_delete(
|
||||
}
|
||||
}
|
||||
|
||||
let result = database::models::Version::remove_full(id.into(), &**pool).await?;
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result = database::models::Version::remove_full(id.into(), &mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
Reference in New Issue
Block a user