You've already forked AstralRinth
forked from didirus/AstralRinth
Fix clippy errors + lint, use turbo CI
This commit is contained in:
@@ -65,19 +65,22 @@ pub async fn page_view_ingest(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
.await
|
||||
.ok();
|
||||
let user =
|
||||
get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
.await
|
||||
.ok();
|
||||
let conn_info = req.connection_info().peer_addr().map(|x| x.to_string());
|
||||
|
||||
let url = Url::parse(&url_input.url)
|
||||
.map_err(|_| ApiError::InvalidInput("invalid page view URL specified!".to_string()))?;
|
||||
let url = Url::parse(&url_input.url).map_err(|_| {
|
||||
ApiError::InvalidInput("invalid page view URL specified!".to_string())
|
||||
})?;
|
||||
|
||||
let domain = url
|
||||
.host_str()
|
||||
.ok_or_else(|| ApiError::InvalidInput("invalid page view URL specified!".to_string()))?;
|
||||
let domain = url.host_str().ok_or_else(|| {
|
||||
ApiError::InvalidInput("invalid page view URL specified!".to_string())
|
||||
})?;
|
||||
|
||||
let allowed_origins = parse_strings_from_var("CORS_ALLOWED_ORIGINS").unwrap_or_default();
|
||||
let allowed_origins =
|
||||
parse_strings_from_var("CORS_ALLOWED_ORIGINS").unwrap_or_default();
|
||||
if !(domain.ends_with(".modrinth.com")
|
||||
|| domain == "modrinth.com"
|
||||
|| allowed_origins.contains(&"*".to_string()))
|
||||
@@ -98,11 +101,13 @@ pub async fn page_view_ingest(
|
||||
})
|
||||
.collect::<HashMap<String, String>>();
|
||||
|
||||
let ip = convert_to_ip_v6(if let Some(header) = headers.get("cf-connecting-ip") {
|
||||
header
|
||||
} else {
|
||||
conn_info.as_deref().unwrap_or_default()
|
||||
})
|
||||
let ip = convert_to_ip_v6(
|
||||
if let Some(header) = headers.get("cf-connecting-ip") {
|
||||
header
|
||||
} else {
|
||||
conn_info.as_deref().unwrap_or_default()
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|_| Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped());
|
||||
|
||||
let mut view = PageView {
|
||||
@@ -135,8 +140,12 @@ pub async fn page_view_ingest(
|
||||
];
|
||||
|
||||
if PROJECT_TYPES.contains(&segments_vec[0]) {
|
||||
let project =
|
||||
crate::database::models::Project::get(segments_vec[1], &**pool, &redis).await?;
|
||||
let project = crate::database::models::Project::get(
|
||||
segments_vec[1],
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(project) = project {
|
||||
view.project_id = project.inner.id.0 as u64;
|
||||
@@ -167,7 +176,9 @@ pub async fn playtime_ingest(
|
||||
req: HttpRequest,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
playtime_input: web::Json<HashMap<crate::models::ids::VersionId, PlaytimeInput>>,
|
||||
playtime_input: web::Json<
|
||||
HashMap<crate::models::ids::VersionId, PlaytimeInput>,
|
||||
>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -200,7 +211,8 @@ pub async fn playtime_ingest(
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(version) = versions.iter().find(|x| id == x.inner.id.into()) {
|
||||
if let Some(version) = versions.iter().find(|x| id == x.inner.id.into())
|
||||
{
|
||||
analytics_queue.add_playtime(Playtime {
|
||||
recorded: get_current_tenths_of_ms(),
|
||||
seconds: playtime.seconds as u64,
|
||||
|
||||
@@ -53,16 +53,25 @@ pub async fn count_download(
|
||||
.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 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 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 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!(
|
||||
"
|
||||
@@ -95,8 +104,9 @@ pub async fn count_download(
|
||||
));
|
||||
};
|
||||
|
||||
let url = url::Url::parse(&download_body.url)
|
||||
.map_err(|_| ApiError::InvalidInput("invalid download URL specified!".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());
|
||||
@@ -127,7 +137,10 @@ pub async fn count_download(
|
||||
.headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|x| !crate::routes::analytics::FILTERED_HEADERS.contains(&&*x.0.to_lowercase()))
|
||||
.filter(|x| {
|
||||
!crate::routes::analytics::FILTERED_HEADERS
|
||||
.contains(&&*x.0.to_lowercase())
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,7 +87,8 @@ impl TempUser {
|
||||
}
|
||||
}
|
||||
|
||||
let user_id = crate::database::models::generate_user_id(transaction).await?;
|
||||
let user_id =
|
||||
crate::database::models::generate_user_id(transaction).await?;
|
||||
|
||||
let mut username_increment: i32 = 0;
|
||||
let mut username = None;
|
||||
@@ -103,7 +104,12 @@ impl TempUser {
|
||||
}
|
||||
);
|
||||
|
||||
let new_id = crate::database::models::User::get(&test_username, client, redis).await?;
|
||||
let new_id = crate::database::models::User::get(
|
||||
&test_username,
|
||||
client,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if new_id.is_none() {
|
||||
username = Some(test_username);
|
||||
@@ -112,71 +118,74 @@ impl TempUser {
|
||||
}
|
||||
}
|
||||
|
||||
let (avatar_url, raw_avatar_url) = if let Some(avatar_url) = self.avatar_url {
|
||||
let res = reqwest::get(&avatar_url).await?;
|
||||
let headers = res.headers().clone();
|
||||
let (avatar_url, raw_avatar_url) =
|
||||
if let Some(avatar_url) = self.avatar_url {
|
||||
let res = reqwest::get(&avatar_url).await?;
|
||||
let headers = res.headers().clone();
|
||||
|
||||
let img_data = if let Some(content_type) = headers
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|ct| ct.to_str().ok())
|
||||
{
|
||||
get_image_ext(content_type)
|
||||
} else {
|
||||
avatar_url.rsplit('.').next()
|
||||
};
|
||||
let img_data = if let Some(content_type) = headers
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|ct| ct.to_str().ok())
|
||||
{
|
||||
get_image_ext(content_type)
|
||||
} else {
|
||||
avatar_url.rsplit('.').next()
|
||||
};
|
||||
|
||||
if let Some(ext) = img_data {
|
||||
let bytes = res.bytes().await?;
|
||||
if let Some(ext) = img_data {
|
||||
let bytes = res.bytes().await?;
|
||||
|
||||
let upload_result = upload_image_optimized(
|
||||
&format!("user/{}", crate::models::users::UserId::from(user_id)),
|
||||
bytes,
|
||||
ext,
|
||||
Some(96),
|
||||
Some(1.0),
|
||||
&**file_host,
|
||||
)
|
||||
.await;
|
||||
let upload_result = upload_image_optimized(
|
||||
&format!(
|
||||
"user/{}",
|
||||
crate::models::users::UserId::from(user_id)
|
||||
),
|
||||
bytes,
|
||||
ext,
|
||||
Some(96),
|
||||
Some(1.0),
|
||||
&**file_host,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(upload_result) = upload_result {
|
||||
(Some(upload_result.url), Some(upload_result.raw_url))
|
||||
if let Ok(upload_result) = upload_result {
|
||||
(Some(upload_result.url), Some(upload_result.raw_url))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
};
|
||||
|
||||
if let Some(username) = username {
|
||||
crate::database::models::User {
|
||||
id: user_id,
|
||||
github_id: if provider == AuthProvider::GitHub {
|
||||
Some(
|
||||
self.id
|
||||
.clone()
|
||||
.parse()
|
||||
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
||||
self.id.clone().parse().map_err(|_| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
discord_id: if provider == AuthProvider::Discord {
|
||||
Some(
|
||||
self.id
|
||||
.parse()
|
||||
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
||||
self.id.parse().map_err(|_| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
gitlab_id: if provider == AuthProvider::GitLab {
|
||||
Some(
|
||||
self.id
|
||||
.parse()
|
||||
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
||||
self.id.parse().map_err(|_| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -188,9 +197,9 @@ impl TempUser {
|
||||
},
|
||||
steam_id: if provider == AuthProvider::Steam {
|
||||
Some(
|
||||
self.id
|
||||
.parse()
|
||||
.map_err(|_| AuthenticationError::InvalidCredentials)?,
|
||||
self.id.parse().map_err(|_| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -236,7 +245,10 @@ impl TempUser {
|
||||
}
|
||||
|
||||
impl AuthProvider {
|
||||
pub fn get_redirect_url(&self, state: String) -> Result<String, AuthenticationError> {
|
||||
pub fn get_redirect_url(
|
||||
&self,
|
||||
state: String,
|
||||
) -> Result<String, AuthenticationError> {
|
||||
let self_addr = dotenvy::var("SELF_ADDR")?;
|
||||
let raw_redirect_uri = format!("{}/v2/auth/callback", self_addr);
|
||||
let redirect_uri = urlencoding::encode(&raw_redirect_uri);
|
||||
@@ -316,7 +328,8 @@ impl AuthProvider {
|
||||
&self,
|
||||
query: HashMap<String, String>,
|
||||
) -> Result<String, AuthenticationError> {
|
||||
let redirect_uri = format!("{}/v2/auth/callback", dotenvy::var("SELF_ADDR")?);
|
||||
let redirect_uri =
|
||||
format!("{}/v2/auth/callback", dotenvy::var("SELF_ADDR")?);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AccessToken {
|
||||
@@ -454,22 +467,26 @@ impl AuthProvider {
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
form.insert(
|
||||
"openid.assoc_handle".to_string(),
|
||||
&**query
|
||||
.get("openid.assoc_handle")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
||||
&**query.get("openid.assoc_handle").ok_or_else(|| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
);
|
||||
form.insert("openid.signed".to_string(), &**signed);
|
||||
form.insert(
|
||||
"openid.sig".to_string(),
|
||||
&**query
|
||||
.get("openid.sig")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
||||
&**query.get("openid.sig").ok_or_else(|| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?,
|
||||
);
|
||||
form.insert(
|
||||
"openid.ns".to_string(),
|
||||
"http://specs.openid.net/auth/2.0",
|
||||
);
|
||||
form.insert("openid.ns".to_string(), "http://specs.openid.net/auth/2.0");
|
||||
form.insert("openid.mode".to_string(), "check_authentication");
|
||||
|
||||
for val in signed.split(',') {
|
||||
if let Some(arr_val) = query.get(&format!("openid.{}", val)) {
|
||||
if let Some(arr_val) = query.get(&format!("openid.{}", val))
|
||||
{
|
||||
form.insert(format!("openid.{}", val), &**arr_val);
|
||||
}
|
||||
}
|
||||
@@ -484,9 +501,10 @@ impl AuthProvider {
|
||||
.await?;
|
||||
|
||||
if res.contains("is_valid:true") {
|
||||
let identity = query
|
||||
.get("openid.identity")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let identity =
|
||||
query.get("openid.identity").ok_or_else(|| {
|
||||
AuthenticationError::InvalidCredentials
|
||||
})?;
|
||||
|
||||
identity
|
||||
.rsplit('/')
|
||||
@@ -533,7 +551,10 @@ impl AuthProvider {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn get_user(&self, token: &str) -> Result<TempUser, AuthenticationError> {
|
||||
pub async fn get_user(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<TempUser, AuthenticationError> {
|
||||
let res = match self {
|
||||
AuthProvider::GitHub => {
|
||||
let response = reqwest::Client::new()
|
||||
@@ -549,7 +570,9 @@ impl AuthProvider {
|
||||
.get("x-oauth-client-id")
|
||||
.and_then(|x| x.to_str().ok());
|
||||
|
||||
if client_id != Some(&*dotenvy::var("GITHUB_CLIENT_ID").unwrap()) {
|
||||
if client_id
|
||||
!= Some(&*dotenvy::var("GITHUB_CLIENT_ID").unwrap())
|
||||
{
|
||||
return Err(AuthenticationError::InvalidClientId);
|
||||
}
|
||||
}
|
||||
@@ -599,9 +622,12 @@ impl AuthProvider {
|
||||
id: discord_user.id,
|
||||
username: discord_user.username,
|
||||
email: discord_user.email,
|
||||
avatar_url: discord_user
|
||||
.avatar
|
||||
.map(|x| format!("https://cdn.discordapp.com/avatars/{}/{}.webp", id, x)),
|
||||
avatar_url: discord_user.avatar.map(|x| {
|
||||
format!(
|
||||
"https://cdn.discordapp.com/avatars/{}/{}.webp",
|
||||
id, x
|
||||
)
|
||||
}),
|
||||
bio: None,
|
||||
country: None,
|
||||
}
|
||||
@@ -727,7 +753,8 @@ impl AuthProvider {
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
let mut response: SteamResponse = serde_json::from_str(&response)?;
|
||||
let mut response: SteamResponse =
|
||||
serde_json::from_str(&response)?;
|
||||
|
||||
if let Some(player) = response.response.players.pop() {
|
||||
let username = player
|
||||
@@ -827,9 +854,12 @@ impl AuthProvider {
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
AuthProvider::Microsoft => {
|
||||
let value = sqlx::query!("SELECT id FROM users WHERE microsoft_id = $1", id)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
let value = sqlx::query!(
|
||||
"SELECT id FROM users WHERE microsoft_id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
@@ -845,9 +875,12 @@ impl AuthProvider {
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
AuthProvider::Google => {
|
||||
let value = sqlx::query!("SELECT id FROM users WHERE google_id = $1", id)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
let value = sqlx::query!(
|
||||
"SELECT id FROM users WHERE google_id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
@@ -863,9 +896,12 @@ impl AuthProvider {
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
AuthProvider::PayPal => {
|
||||
let value = sqlx::query!("SELECT id FROM users WHERE paypal_id = $1", id)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
let value = sqlx::query!(
|
||||
"SELECT id FROM users WHERE paypal_id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(executor)
|
||||
.await?;
|
||||
|
||||
value.map(|x| crate::database::models::UserId(x.id))
|
||||
}
|
||||
@@ -1024,11 +1060,15 @@ pub async fn init(
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, AuthenticationError> {
|
||||
let url = url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
||||
let url =
|
||||
url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
||||
|
||||
let allowed_callback_urls = parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||
let allowed_callback_urls =
|
||||
parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||
let domain = url.host_str().ok_or(AuthenticationError::Url)?;
|
||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x)) && domain != "modrinth.com" {
|
||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x))
|
||||
&& domain != "modrinth.com"
|
||||
{
|
||||
return Err(AuthenticationError::Url);
|
||||
}
|
||||
|
||||
@@ -1381,7 +1421,11 @@ pub async fn delete_auth_provider(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(
|
||||
&[(user.id.into(), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -1431,24 +1475,28 @@ pub async fn create_account_with_password(
|
||||
redis: Data<RedisPool>,
|
||||
new_account: web::Json<NewAccount>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
new_account
|
||||
.0
|
||||
.validate()
|
||||
.map_err(|err| ApiError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
new_account.0.validate().map_err(|err| {
|
||||
ApiError::InvalidInput(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
if !check_turnstile_captcha(&req, &new_account.challenge).await? {
|
||||
return Err(ApiError::Turnstile);
|
||||
}
|
||||
|
||||
if crate::database::models::User::get(&new_account.username, &**pool, &redis)
|
||||
.await?
|
||||
.is_some()
|
||||
if crate::database::models::User::get(
|
||||
&new_account.username,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::InvalidInput("Username is taken!".to_string()));
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||
let user_id =
|
||||
crate::database::models::generate_user_id(&mut transaction).await?;
|
||||
|
||||
let new_account = new_account.0;
|
||||
|
||||
@@ -1459,10 +1507,13 @@ pub async fn create_account_with_password(
|
||||
|
||||
if score.score() < 3 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
if let Some(feedback) = score.feedback().clone().and_then(|x| x.warning()) {
|
||||
if let Some(feedback) =
|
||||
score.feedback().clone().and_then(|x| x.warning())
|
||||
{
|
||||
format!("Password too weak: {}", feedback)
|
||||
} else {
|
||||
"Specified password is too weak! Please improve its strength.".to_string()
|
||||
"Specified password is too weak! Please improve its strength."
|
||||
.to_string()
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -1554,13 +1605,15 @@ pub async fn login_password(
|
||||
}
|
||||
|
||||
let user = if let Some(user) =
|
||||
crate::database::models::User::get(&login.username, &**pool, &redis).await?
|
||||
crate::database::models::User::get(&login.username, &**pool, &redis)
|
||||
.await?
|
||||
{
|
||||
user
|
||||
} else {
|
||||
let user = crate::database::models::User::get_email(&login.username, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let user =
|
||||
crate::database::models::User::get_email(&login.username, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
crate::database::models::User::get_id(user, &**pool, &redis)
|
||||
.await?
|
||||
@@ -1591,7 +1644,8 @@ pub async fn login_password(
|
||||
})))
|
||||
} else {
|
||||
let mut transaction = pool.begin().await?;
|
||||
let session = issue_session(req, user.id, &mut transaction, &redis).await?;
|
||||
let session =
|
||||
issue_session(req, user.id, &mut transaction, &redis).await?;
|
||||
let res = crate::models::sessions::Session::from(session, true, None);
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -1651,7 +1705,9 @@ async fn validate_2fa_code(
|
||||
|
||||
Ok(true)
|
||||
} else if allow_backup {
|
||||
let backup_codes = crate::database::models::User::get_backup_codes(user_id, pool).await?;
|
||||
let backup_codes =
|
||||
crate::database::models::User::get_backup_codes(user_id, pool)
|
||||
.await?;
|
||||
|
||||
if !backup_codes.contains(&input) {
|
||||
Ok(false)
|
||||
@@ -1669,7 +1725,11 @@ async fn validate_2fa_code(
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user_id, None)], redis).await?;
|
||||
crate::database::models::User::clear_caches(
|
||||
&[(user_id, None)],
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -1690,9 +1750,10 @@ pub async fn login_2fa(
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
if let Flow::Login2FA { user_id } = flow {
|
||||
let user = crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let user =
|
||||
crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
if !validate_2fa_code(
|
||||
@@ -1713,7 +1774,8 @@ pub async fn login_2fa(
|
||||
}
|
||||
Flow::remove(&login.flow, &redis).await?;
|
||||
|
||||
let session = issue_session(req, user_id, &mut transaction, &redis).await?;
|
||||
let session =
|
||||
issue_session(req, user_id, &mut transaction, &redis).await?;
|
||||
let res = crate::models::sessions::Session::from(session, true, None);
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -1870,7 +1932,11 @@ pub async fn finish_2fa_flow(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(
|
||||
&[(user.id.into(), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"backup_codes": codes,
|
||||
@@ -1895,10 +1961,15 @@ pub async fn remove_2fa(
|
||||
login: web::Json<Remove2FA>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (scopes, user) =
|
||||
get_user_record_from_bearer_token(&req, None, &**pool, &redis, &session_queue)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let (scopes, user) = get_user_record_from_bearer_token(
|
||||
&req,
|
||||
None,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
if !scopes.contains(Scopes::USER_AUTH_WRITE) {
|
||||
return Err(ApiError::Authentication(
|
||||
@@ -1911,7 +1982,9 @@ pub async fn remove_2fa(
|
||||
if !validate_2fa_code(
|
||||
login.code.clone(),
|
||||
user.totp_secret.ok_or_else(|| {
|
||||
ApiError::InvalidInput("User does not have 2FA enabled on the account!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"User does not have 2FA enabled on the account!".to_string(),
|
||||
)
|
||||
})?,
|
||||
true,
|
||||
user.id,
|
||||
@@ -1958,7 +2031,8 @@ pub async fn remove_2fa(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -1980,12 +2054,20 @@ pub async fn reset_password_begin(
|
||||
return Err(ApiError::Turnstile);
|
||||
}
|
||||
|
||||
let user = if let Some(user_id) =
|
||||
crate::database::models::User::get_email(&reset_password.username, &**pool).await?
|
||||
let user = if let Some(user_id) = crate::database::models::User::get_email(
|
||||
&reset_password.username,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
crate::database::models::User::get_id(user_id, &**pool, &redis).await?
|
||||
} else {
|
||||
crate::database::models::User::get(&reset_password.username, &**pool, &redis).await?
|
||||
crate::database::models::User::get(
|
||||
&reset_password.username,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
if let Some(user) = user {
|
||||
@@ -2026,9 +2108,10 @@ pub async fn change_password(
|
||||
let flow = Flow::get(flow, &redis).await?;
|
||||
|
||||
if let Some(Flow::ForgotPassword { user_id }) = flow {
|
||||
let user = crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let user =
|
||||
crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
Some(user)
|
||||
} else {
|
||||
@@ -2041,10 +2124,15 @@ pub async fn change_password(
|
||||
let user = if let Some(user) = user {
|
||||
user
|
||||
} else {
|
||||
let (scopes, user) =
|
||||
get_user_record_from_bearer_token(&req, None, &**pool, &redis, &session_queue)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let (scopes, user) = get_user_record_from_bearer_token(
|
||||
&req,
|
||||
None,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
if !scopes.contains(Scopes::USER_AUTH_WRITE) {
|
||||
return Err(ApiError::Authentication(
|
||||
@@ -2060,7 +2148,10 @@ pub async fn change_password(
|
||||
})?;
|
||||
|
||||
let hasher = Argon2::default();
|
||||
hasher.verify_password(old_password.as_bytes(), &PasswordHash::new(pass)?)?;
|
||||
hasher.verify_password(
|
||||
old_password.as_bytes(),
|
||||
&PasswordHash::new(pass)?,
|
||||
)?;
|
||||
}
|
||||
|
||||
user
|
||||
@@ -2068,7 +2159,9 @@ pub async fn change_password(
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let update_password = if let Some(new_password) = &change_password.new_password {
|
||||
let update_password = if let Some(new_password) =
|
||||
&change_password.new_password
|
||||
{
|
||||
let score = zxcvbn::zxcvbn(
|
||||
new_password,
|
||||
&[&user.username, &user.email.clone().unwrap_or_default()],
|
||||
@@ -2076,7 +2169,9 @@ pub async fn change_password(
|
||||
|
||||
if score.score() < 3 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
if let Some(feedback) = score.feedback().clone().and_then(|x| x.warning()) {
|
||||
if let Some(feedback) =
|
||||
score.feedback().clone().and_then(|x| x.warning())
|
||||
{
|
||||
format!("Password too weak: {}", feedback)
|
||||
} else {
|
||||
"Specified password is too weak! Please improve its strength.".to_string()
|
||||
@@ -2140,7 +2235,8 @@ pub async fn change_password(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
@@ -2160,10 +2256,9 @@ pub async fn set_email(
|
||||
session_queue: Data<AuthQueue>,
|
||||
stripe_client: Data<stripe::Client>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
email
|
||||
.0
|
||||
.validate()
|
||||
.map_err(|err| ApiError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
email.0.validate().map_err(|err| {
|
||||
ApiError::InvalidInput(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
@@ -2229,7 +2324,11 @@ pub async fn set_email(
|
||||
)?;
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(
|
||||
&[(user.id.into(), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
@@ -2265,7 +2364,11 @@ pub async fn resend_verify_email(
|
||||
.insert(Duration::hours(24), &redis)
|
||||
.await?;
|
||||
|
||||
send_email_verify(email, flow, "We need to verify your email address.")?;
|
||||
send_email_verify(
|
||||
email,
|
||||
flow,
|
||||
"We need to verify your email address.",
|
||||
)?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
} else {
|
||||
@@ -2293,9 +2396,10 @@ pub async fn verify_email(
|
||||
confirm_email,
|
||||
}) = flow
|
||||
{
|
||||
let user = crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let user =
|
||||
crate::database::models::User::get_id(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
if user.email != Some(confirm_email) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -2319,12 +2423,14 @@ pub async fn verify_email(
|
||||
|
||||
Flow::remove(&email.flow, &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
} else {
|
||||
Err(ApiError::InvalidInput(
|
||||
"Flow does not exist. Try re-requesting the verification link.".to_string(),
|
||||
"Flow does not exist. Try re-requesting the verification link."
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,18 @@ pub async fn export(
|
||||
|
||||
let user_id = user.id.into();
|
||||
|
||||
let collection_ids = crate::database::models::User::get_collections(user_id, &**pool).await?;
|
||||
let collections =
|
||||
crate::database::models::Collection::get_many(&collection_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::collections::Collection::from)
|
||||
.collect::<Vec<_>>();
|
||||
let collection_ids =
|
||||
crate::database::models::User::get_collections(user_id, &**pool)
|
||||
.await?;
|
||||
let collections = crate::database::models::Collection::get_many(
|
||||
&collection_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::collections::Collection::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let follows = crate::database::models::User::get_follows(user_id, &**pool)
|
||||
.await?
|
||||
@@ -43,22 +48,26 @@ pub async fn export(
|
||||
.map(crate::models::ids::ProjectId::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let projects = crate::database::models::User::get_projects(user_id, &**pool, &redis)
|
||||
let projects =
|
||||
crate::database::models::User::get_projects(user_id, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::ids::ProjectId::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let org_ids =
|
||||
crate::database::models::User::get_organizations(user_id, &**pool)
|
||||
.await?;
|
||||
let orgs =
|
||||
crate::database::models::organization_item::Organization::get_many_ids(
|
||||
&org_ids, &**pool, &redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::ids::ProjectId::from)
|
||||
// TODO: add team members
|
||||
.map(|x| crate::models::organizations::Organization::from(x, vec![]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let org_ids = crate::database::models::User::get_organizations(user_id, &**pool).await?;
|
||||
let orgs = crate::database::models::organization_item::Organization::get_many_ids(
|
||||
&org_ids, &**pool, &redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
// TODO: add team members
|
||||
.map(|x| crate::models::organizations::Organization::from(x, vec![]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let notifs = crate::database::models::notification_item::Notification::get_many_user(
|
||||
user_id, &**pool, &redis,
|
||||
)
|
||||
@@ -84,34 +93,46 @@ pub async fn export(
|
||||
.map(crate::models::oauth_clients::OAuthClientAuthorization::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pat_ids = crate::database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||
user_id, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
let pats = crate::database::models::pat_item::PersonalAccessToken::get_many_ids(
|
||||
&pat_ids, &**pool, &redis,
|
||||
let pat_ids =
|
||||
crate::database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||
user_id, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
let pats =
|
||||
crate::database::models::pat_item::PersonalAccessToken::get_many_ids(
|
||||
&pat_ids, &**pool, &redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| crate::models::pats::PersonalAccessToken::from(x, false))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let payout_ids =
|
||||
crate::database::models::payout_item::Payout::get_all_for_user(
|
||||
user_id, &**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let payouts = crate::database::models::payout_item::Payout::get_many(
|
||||
&payout_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| crate::models::pats::PersonalAccessToken::from(x, false))
|
||||
.map(crate::models::payouts::Payout::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let payout_ids =
|
||||
crate::database::models::payout_item::Payout::get_all_for_user(user_id, &**pool).await?;
|
||||
|
||||
let payouts = crate::database::models::payout_item::Payout::get_many(&payout_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::payouts::Payout::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let report_ids =
|
||||
crate::database::models::user_item::User::get_reports(user_id, &**pool).await?;
|
||||
let reports = crate::database::models::report_item::Report::get_many(&report_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::reports::Report::from)
|
||||
.collect::<Vec<_>>();
|
||||
crate::database::models::user_item::User::get_reports(user_id, &**pool)
|
||||
.await?;
|
||||
let reports = crate::database::models::report_item::Report::get_many(
|
||||
&report_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::reports::Report::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let message_ids = sqlx::query!(
|
||||
"
|
||||
@@ -126,11 +147,14 @@ pub async fn export(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let messages =
|
||||
crate::database::models::thread_item::ThreadMessage::get_many(&message_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| crate::models::threads::ThreadMessage::from(x, &user))
|
||||
.collect::<Vec<_>>();
|
||||
crate::database::models::thread_item::ThreadMessage::get_many(
|
||||
&message_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| crate::models::threads::ThreadMessage::from(x, &user))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let uploaded_images_ids = sqlx::query!(
|
||||
"SELECT id FROM uploaded_images WHERE owner_id = $1",
|
||||
@@ -142,12 +166,15 @@ pub async fn export(
|
||||
.map(|x| crate::database::models::ids::ImageId(x.id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let uploaded_images =
|
||||
crate::database::models::image_item::Image::get_many(&uploaded_images_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::images::Image::from)
|
||||
.collect::<Vec<_>>();
|
||||
let uploaded_images = crate::database::models::image_item::Image::get_many(
|
||||
&uploaded_images_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::images::Image::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let subscriptions =
|
||||
crate::database::models::user_subscription_item::UserSubscriptionItem::get_all_user(
|
||||
|
||||
@@ -60,11 +60,12 @@ pub async fn get_projects(
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<_> = database::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::projects::Project::from)
|
||||
.collect();
|
||||
let projects: Vec<_> =
|
||||
database::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::models::projects::Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
@@ -86,7 +87,8 @@ pub async fn get_project_meta(
|
||||
.await?;
|
||||
|
||||
let project_id = info.into_inner().0;
|
||||
let project = database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||
let project =
|
||||
database::models::Project::get(&project_id, &**pool, &redis).await?;
|
||||
|
||||
if let Some(project) = project {
|
||||
let rows = sqlx::query!(
|
||||
@@ -122,7 +124,8 @@ pub async fn get_project_meta(
|
||||
|
||||
check_hashes.extend(merged.flame_files.keys().cloned());
|
||||
check_hashes.extend(merged.unknown_files.keys().cloned());
|
||||
check_flames.extend(merged.flame_files.values().map(|x| x.id as i32));
|
||||
check_flames
|
||||
.extend(merged.flame_files.values().map(|x| x.id as i32));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,15 +44,17 @@ pub async fn get_pats(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let pat_ids = database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
&redis,
|
||||
let pat_ids =
|
||||
database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let pats = database::models::pat_item::PersonalAccessToken::get_many_ids(
|
||||
&pat_ids, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
let pats =
|
||||
database::models::pat_item::PersonalAccessToken::get_many_ids(&pat_ids, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(
|
||||
pats.into_iter()
|
||||
@@ -77,9 +79,9 @@ pub async fn create_pat(
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
info.0
|
||||
.validate()
|
||||
.map_err(|err| ApiError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
info.0.validate().map_err(|err| {
|
||||
ApiError::InvalidInput(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
if info.scopes.is_restricted() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -174,7 +176,10 @@ pub async fn edit_pat(
|
||||
.1;
|
||||
|
||||
let id = id.into_inner().0;
|
||||
let pat = database::models::pat_item::PersonalAccessToken::get(&id, &**pool, &redis).await?;
|
||||
let pat = database::models::pat_item::PersonalAccessToken::get(
|
||||
&id, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(pat) = pat {
|
||||
if pat.user_id == user.id.into() {
|
||||
@@ -262,13 +267,19 @@ pub async fn delete_pat(
|
||||
.await?
|
||||
.1;
|
||||
let id = id.into_inner().0;
|
||||
let pat = database::models::pat_item::PersonalAccessToken::get(&id, &**pool, &redis).await?;
|
||||
let pat = database::models::pat_item::PersonalAccessToken::get(
|
||||
&id, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(pat) = pat {
|
||||
if pat.user_id == user.id.into() {
|
||||
let mut transaction = pool.begin().await?;
|
||||
database::models::pat_item::PersonalAccessToken::remove(pat.id, &mut transaction)
|
||||
.await?;
|
||||
database::models::pat_item::PersonalAccessToken::remove(
|
||||
pat.id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||
|
||||
@@ -152,7 +152,9 @@ pub async fn list(
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
|
||||
let session_ids = DBSession::get_user_sessions(current_user.id.into(), &**pool, &redis).await?;
|
||||
let session_ids =
|
||||
DBSession::get_user_sessions(current_user.id.into(), &**pool, &redis)
|
||||
.await?;
|
||||
let sessions = DBSession::get_many_ids(&session_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -210,19 +212,24 @@ pub async fn refresh(
|
||||
redis: Data<RedisPool>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
.await?
|
||||
.1;
|
||||
let current_user =
|
||||
get_user_from_headers(&req, &**pool, &redis, &session_queue, None)
|
||||
.await?
|
||||
.1;
|
||||
let session = req
|
||||
.headers()
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::Authentication(AuthenticationError::InvalidCredentials))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::Authentication(AuthenticationError::InvalidCredentials)
|
||||
})?;
|
||||
|
||||
let session = DBSession::get(session, &**pool, &redis).await?;
|
||||
|
||||
if let Some(session) = session {
|
||||
if current_user.id != session.user_id.into() || session.refresh_expires < Utc::now() {
|
||||
if current_user.id != session.user_id.into()
|
||||
|| session.refresh_expires < Utc::now()
|
||||
{
|
||||
return Err(ApiError::Authentication(
|
||||
AuthenticationError::InvalidCredentials,
|
||||
));
|
||||
@@ -231,7 +238,9 @@ pub async fn refresh(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
DBSession::remove(session.id, &mut transaction).await?;
|
||||
let new_session = issue_session(req, session.user_id, &mut transaction, &redis).await?;
|
||||
let new_session =
|
||||
issue_session(req, session.user_id, &mut transaction, &redis)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
DBSession::clear_cache(
|
||||
vec![(
|
||||
|
||||
@@ -77,7 +77,9 @@ pub async fn maven_metadata(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let project_id = params.into_inner().0;
|
||||
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
|
||||
let Some(project) =
|
||||
database::models::Project::get(&project_id, &**pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -145,7 +147,11 @@ pub async fn maven_metadata(
|
||||
versions: Versions {
|
||||
versions: new_versions,
|
||||
},
|
||||
last_updated: project.inner.updated.format("%Y%m%d%H%M%S").to_string(),
|
||||
last_updated: project
|
||||
.inner
|
||||
.updated
|
||||
.format("%Y%m%d%H%M%S")
|
||||
.to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -164,11 +170,16 @@ async fn find_version(
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
let all_versions = database::models::Version::get_many(&project.versions, pool, redis).await?;
|
||||
let all_versions =
|
||||
database::models::Version::get_many(&project.versions, pool, redis)
|
||||
.await?;
|
||||
|
||||
let exact_matches = all_versions
|
||||
.iter()
|
||||
.filter(|x| &x.inner.version_number == vcoords || Some(x.inner.id.0) == id_option)
|
||||
.filter(|x| {
|
||||
&x.inner.version_number == vcoords
|
||||
|| Some(x.inner.id.0) == id_option
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if exact_matches.len() == 1 {
|
||||
@@ -202,11 +213,10 @@ async fn find_version(
|
||||
|
||||
// For maven in particular, we will hardcode it to use GameVersions rather than generic loader fields, as this is minecraft-java exclusive
|
||||
if !game_versions.is_empty() {
|
||||
let version_game_versions = x
|
||||
.version_fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.find_map(|v| MinecraftGameVersion::try_from_version_field(&v).ok());
|
||||
let version_game_versions =
|
||||
x.version_fields.clone().into_iter().find_map(|v| {
|
||||
MinecraftGameVersion::try_from_version_field(&v).ok()
|
||||
});
|
||||
if let Some(version_game_versions) = version_game_versions {
|
||||
bool &= version_game_versions
|
||||
.iter()
|
||||
@@ -231,7 +241,9 @@ fn find_file<'a>(
|
||||
version: &'a QueryVersion,
|
||||
file: &str,
|
||||
) -> Option<&'a QueryFile> {
|
||||
if let Some(selected_file) = version.files.iter().find(|x| x.filename == file) {
|
||||
if let Some(selected_file) =
|
||||
version.files.iter().find(|x| x.filename == file)
|
||||
{
|
||||
return Some(selected_file);
|
||||
}
|
||||
|
||||
@@ -271,7 +283,9 @@ pub async fn version_file(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
|
||||
let Some(project) =
|
||||
database::models::Project::get(&project_id, &**pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -290,7 +304,8 @@ pub async fn version_file(
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -314,7 +329,9 @@ pub async fn version_file(
|
||||
return Ok(HttpResponse::Ok()
|
||||
.content_type("text/xml")
|
||||
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
|
||||
} else if let Some(selected_file) = find_file(&project_id, &vnum, &version, &file) {
|
||||
} else if let Some(selected_file) =
|
||||
find_file(&project_id, &vnum, &version, &file)
|
||||
{
|
||||
return Ok(HttpResponse::TemporaryRedirect()
|
||||
.append_header(("location", &*selected_file.url))
|
||||
.body(""));
|
||||
@@ -332,7 +349,9 @@ pub async fn version_file_sha1(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
|
||||
let Some(project) =
|
||||
database::models::Project::get(&project_id, &**pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -351,7 +370,8 @@ pub async fn version_file_sha1(
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -374,7 +394,9 @@ pub async fn version_file_sha512(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (project_id, vnum, file) = params.into_inner();
|
||||
let Some(project) = database::models::Project::get(&project_id, &**pool, &redis).await? else {
|
||||
let Some(project) =
|
||||
database::models::Project::get(&project_id, &**pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
@@ -393,7 +415,8 @@ pub async fn version_file_sha512(
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await? else {
|
||||
let Some(version) = find_version(&project, &vnum, &pool, &redis).await?
|
||||
else {
|
||||
return Err(ApiError::NotFound);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,11 +39,16 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
Cors::default()
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
let allowed_origins =
|
||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS").unwrap_or_default();
|
||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_default();
|
||||
|
||||
allowed_origins.contains(&"*".to_string())
|
||||
|| allowed_origins
|
||||
.contains(&origin.to_str().unwrap_or_default().to_string())
|
||||
|| allowed_origins.contains(
|
||||
&origin
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.allowed_methods(vec!["GET", "POST"])
|
||||
.allowed_headers(vec![
|
||||
|
||||
@@ -61,7 +61,9 @@ pub async fn forge_updates(
|
||||
return Err(ApiError::InvalidInput(ERROR.to_string()));
|
||||
}
|
||||
|
||||
let versions = database::models::Version::get_many(&project.versions, &**pool, &redis).await?;
|
||||
let versions =
|
||||
database::models::Version::get_many(&project.versions, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
let loaders = match &*neo.neoforge {
|
||||
"only" => |x: &String| *x == "neoforge",
|
||||
@@ -105,7 +107,9 @@ pub async fn forge_updates(
|
||||
.fields
|
||||
.iter()
|
||||
.find(|(key, _)| key.as_str() == MinecraftGameVersion::FIELD_NAME)
|
||||
.and_then(|(_, value)| serde_json::from_value::<Vec<String>>(value.clone()).ok())
|
||||
.and_then(|(_, value)| {
|
||||
serde_json::from_value::<Vec<String>>(value.clone()).ok()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if version.version_type == VersionType::Release {
|
||||
|
||||
@@ -43,7 +43,8 @@ pub async fn get_projects(
|
||||
// Convert to V2 projects
|
||||
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
|
||||
Ok(project) => {
|
||||
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(legacy_projects))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
|
||||
@@ -65,9 +65,15 @@ pub async fn notification_get(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::notifications::notification_get(req, info, pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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);
|
||||
@@ -100,9 +106,15 @@ pub async fn notification_delete(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so no need to convert
|
||||
v3::notifications::notification_delete(req, info, pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
v3::notifications::notification_delete(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[patch("notifications")]
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::projects::{Loader, Project, ProjectStatus};
|
||||
use crate::models::v2::projects::{DonationLink, LegacyProject, LegacySideType};
|
||||
use crate::models::v2::projects::{
|
||||
DonationLink, LegacyProject, LegacySideType,
|
||||
};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::v3::project_creation::default_project_type;
|
||||
use crate::routes::v3::project_creation::{CreateError, NewGalleryItem};
|
||||
@@ -158,13 +160,22 @@ pub async fn project_create(
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let mut fields = HashMap::new();
|
||||
fields.extend(v2_reroute::convert_side_types_v3(client_side, server_side));
|
||||
fields.insert("game_versions".to_string(), json!(v.game_versions));
|
||||
fields.extend(v2_reroute::convert_side_types_v3(
|
||||
client_side,
|
||||
server_side,
|
||||
));
|
||||
fields.insert(
|
||||
"game_versions".to_string(),
|
||||
json!(v.game_versions),
|
||||
);
|
||||
|
||||
// Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
|
||||
// Setting of 'project_type' directly is removed, it's loader-based now.
|
||||
if project_type == "modpack" {
|
||||
fields.insert("mrpack_loaders".to_string(), json!(v.loaders));
|
||||
fields.insert(
|
||||
"mrpack_loaders".to_string(),
|
||||
json!(v.loaders),
|
||||
);
|
||||
}
|
||||
|
||||
let loaders = if project_type == "modpack" {
|
||||
@@ -248,7 +259,10 @@ pub async fn project_create(
|
||||
match v2_reroute::extract_ok_json::<Project>(response).await {
|
||||
Ok(project) => {
|
||||
let version_item = match project.versions.first() {
|
||||
Some(vid) => version_item::Version::get((*vid).into(), &**client, &redis).await?,
|
||||
Some(vid) => {
|
||||
version_item::Version::get((*vid).into(), &**client, &redis)
|
||||
.await?
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let project = LegacyProject::from(project, version_item);
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::file_hosting::FileHost;
|
||||
use crate::models::projects::{
|
||||
Link, MonetizationStatus, Project, ProjectStatus, SearchRequest, Version,
|
||||
};
|
||||
use crate::models::v2::projects::{DonationLink, LegacyProject, LegacySideType, LegacyVersion};
|
||||
use crate::models::v2::projects::{
|
||||
DonationLink, LegacyProject, LegacySideType, LegacyVersion,
|
||||
};
|
||||
use crate::models::v2::search::LegacySearchResults;
|
||||
use crate::queue::moderation::AutomatedModerationQueue;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -71,7 +73,9 @@ pub async fn project_search(
|
||||
facet
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
if let Some((key, operator, val)) = parse_facet(&facet) {
|
||||
if let Some((key, operator, val)) =
|
||||
parse_facet(&facet)
|
||||
{
|
||||
format!(
|
||||
"{}{}{}",
|
||||
match key.as_str() {
|
||||
@@ -155,15 +159,19 @@ pub async fn random_projects_get(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let count = v3::projects::RandomProjects { count: count.count };
|
||||
|
||||
let response =
|
||||
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)?;
|
||||
let response = 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) => {
|
||||
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(legacy_projects))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -193,7 +201,8 @@ pub async fn projects_get(
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
|
||||
Ok(project) => {
|
||||
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(legacy_projects))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -210,15 +219,24 @@ pub async fn project_get(
|
||||
) -> 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
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::projects::project_get(
|
||||
req,
|
||||
info,
|
||||
pool.clone(),
|
||||
redis.clone(),
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Project>(response).await {
|
||||
Ok(project) => {
|
||||
let version_item = match project.versions.first() {
|
||||
Some(vid) => version_item::Version::get((*vid).into(), &**pool, &redis).await?,
|
||||
Some(vid) => {
|
||||
version_item::Version::get((*vid).into(), &**pool, &redis)
|
||||
.await?
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let project = LegacyProject::from(project, version_item);
|
||||
@@ -256,16 +274,28 @@ pub async fn dependency_list(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// TODO: tests, probably
|
||||
let response =
|
||||
v3::projects::dependency_list(req, info, pool.clone(), redis.clone(), session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::projects::dependency_list(
|
||||
req,
|
||||
info,
|
||||
pool.clone(),
|
||||
redis.clone(),
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
match v2_reroute::extract_ok_json::<crate::routes::v3::projects::DependencyInfo>(response).await
|
||||
match v2_reroute::extract_ok_json::<
|
||||
crate::routes::v3::projects::DependencyInfo,
|
||||
>(response)
|
||||
.await
|
||||
{
|
||||
Ok(dependency_info) => {
|
||||
let converted_projects =
|
||||
LegacyProject::from_many(dependency_info.projects, &**pool, &redis).await?;
|
||||
let converted_projects = LegacyProject::from_many(
|
||||
dependency_info.projects,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let converted_versions = dependency_info
|
||||
.versions
|
||||
.into_iter()
|
||||
@@ -443,7 +473,8 @@ pub async fn project_edit(
|
||||
// (resetting to the new ones)
|
||||
if let Some(donation_urls) = v2_new_project.donation_urls {
|
||||
// Fetch current donation links from project so we know what to delete
|
||||
let fetched_example_project = project_item::Project::get(&info.0, &**pool, &redis).await?;
|
||||
let fetched_example_project =
|
||||
project_item::Project::get(&info.0, &**pool, &redis).await?;
|
||||
let donation_links = fetched_example_project
|
||||
.map(|x| {
|
||||
x.urls
|
||||
@@ -504,11 +535,19 @@ pub async fn project_edit(
|
||||
|
||||
// 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.
|
||||
if response.status().is_success() && (client_side.is_some() || server_side.is_some()) {
|
||||
let project_item =
|
||||
project_item::Project::get(&new_slug.unwrap_or(project_id), &**pool, &redis).await?;
|
||||
if response.status().is_success()
|
||||
&& (client_side.is_some() || server_side.is_some())
|
||||
{
|
||||
let project_item = project_item::Project::get(
|
||||
&new_slug.unwrap_or(project_id),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let version_ids = project_item.map(|x| x.versions).unwrap_or_default();
|
||||
let versions = version_item::Version::get_many(&version_ids, &**pool, &redis).await?;
|
||||
let versions =
|
||||
version_item::Version::get_many(&version_ids, &**pool, &redis)
|
||||
.await?;
|
||||
for version in versions {
|
||||
let version = Version::from(version);
|
||||
let mut fields = version.fields;
|
||||
@@ -516,7 +555,10 @@ pub async fn project_edit(
|
||||
v2_reroute::convert_side_types_v2(&fields, None);
|
||||
let client_side = client_side.unwrap_or(current_client_side);
|
||||
let server_side = server_side.unwrap_or(current_server_side);
|
||||
fields.extend(v2_reroute::convert_side_types_v3(client_side, server_side));
|
||||
fields.extend(v2_reroute::convert_side_types_v3(
|
||||
client_side,
|
||||
server_side,
|
||||
));
|
||||
|
||||
response = v3::versions::version_edit_helper(
|
||||
req.clone(),
|
||||
@@ -682,8 +724,10 @@ pub async fn projects_edit(
|
||||
add_categories: bulk_edit_project.add_categories,
|
||||
remove_categories: bulk_edit_project.remove_categories,
|
||||
additional_categories: bulk_edit_project.additional_categories,
|
||||
add_additional_categories: bulk_edit_project.add_additional_categories,
|
||||
remove_additional_categories: bulk_edit_project.remove_additional_categories,
|
||||
add_additional_categories: bulk_edit_project
|
||||
.add_additional_categories,
|
||||
remove_additional_categories: bulk_edit_project
|
||||
.remove_additional_categories,
|
||||
link_urls: Some(link_urls),
|
||||
}),
|
||||
redis,
|
||||
@@ -735,9 +779,16 @@ pub async fn delete_project_icon(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so no need to convert
|
||||
v3::projects::delete_project_icon(req, info, pool, redis, file_host, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
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)]
|
||||
@@ -873,9 +924,16 @@ pub async fn project_delete(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so no need to convert
|
||||
v3::projects::project_delete(req, info, pool, redis, search_config, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
v3::projects::project_delete(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
search_config,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[post("{id}/follow")]
|
||||
|
||||
@@ -25,9 +25,10 @@ pub async fn report_create(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::reports::report_create(req, pool, body, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response =
|
||||
v3::reports::report_create(req, pool, body, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Report>(response).await {
|
||||
@@ -78,7 +79,8 @@ pub async fn reports(
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<Report>>(response).await {
|
||||
Ok(reports) => {
|
||||
let reports: Vec<_> = reports.into_iter().map(LegacyReport::from).collect();
|
||||
let reports: Vec<_> =
|
||||
reports.into_iter().map(LegacyReport::from).collect();
|
||||
Ok(HttpResponse::Ok().json(reports))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -111,7 +113,8 @@ pub async fn reports_get(
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<Report>>(response).await {
|
||||
Ok(report_list) => {
|
||||
let report_list: Vec<_> = report_list.into_iter().map(LegacyReport::from).collect();
|
||||
let report_list: Vec<_> =
|
||||
report_list.into_iter().map(LegacyReport::from).collect();
|
||||
Ok(HttpResponse::Ok().json(report_list))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -126,9 +129,10 @@ pub async fn report_get(
|
||||
info: web::Path<(crate::models::reports::ReportId,)>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::reports::report_get(req, pool, redis, info, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response =
|
||||
v3::reports::report_get(req, pool, redis, info, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Report>(response).await {
|
||||
|
||||
@@ -19,7 +19,9 @@ pub struct V2Stats {
|
||||
}
|
||||
|
||||
#[get("statistics")]
|
||||
pub async fn get_stats(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn get_stats(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::statistics::get_stats(pool)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
@@ -43,7 +43,9 @@ pub async fn category_list(
|
||||
let response = v3::tags::category_list(pool, redis).await?;
|
||||
|
||||
// Convert to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::CategoryData>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::CategoryData>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(categories) => {
|
||||
let categories = categories
|
||||
.into_iter()
|
||||
@@ -75,7 +77,9 @@ pub async fn loader_list(
|
||||
let response = v3::tags::loader_list(pool, redis).await?;
|
||||
|
||||
// Convert to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(loaders) => {
|
||||
let loaders = loaders
|
||||
.into_iter()
|
||||
@@ -86,12 +90,15 @@ pub async fn loader_list(
|
||||
// a project type before any versions are set.
|
||||
supported_project_types.push("project".to_string());
|
||||
|
||||
if ["forge", "fabric", "quilt", "neoforge"].contains(&&*l.name) {
|
||||
if ["forge", "fabric", "quilt", "neoforge"]
|
||||
.contains(&&*l.name)
|
||||
{
|
||||
supported_project_types.push("modpack".to_string());
|
||||
}
|
||||
|
||||
if supported_project_types.contains(&"datapack".to_string())
|
||||
|| supported_project_types.contains(&"plugin".to_string())
|
||||
|| supported_project_types
|
||||
.contains(&"plugin".to_string())
|
||||
{
|
||||
supported_project_types.push("mod".to_string());
|
||||
}
|
||||
@@ -149,7 +156,9 @@ pub async fn game_version_list(
|
||||
|
||||
// Convert to V2 format
|
||||
Ok(
|
||||
match v2_reroute::extract_ok_json::<Vec<LoaderFieldEnumValue>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<Vec<LoaderFieldEnumValue>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(fields) => {
|
||||
let fields = fields
|
||||
.into_iter()
|
||||
@@ -187,7 +196,8 @@ pub async fn license_list() -> HttpResponse {
|
||||
let response = v3::tags::license_list().await;
|
||||
|
||||
// Convert to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::License>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<Vec<v3::tags::License>>(response).await
|
||||
{
|
||||
Ok(licenses) => {
|
||||
let licenses = licenses
|
||||
.into_iter()
|
||||
@@ -209,14 +219,18 @@ pub struct LicenseText {
|
||||
}
|
||||
|
||||
#[get("license/{id}")]
|
||||
pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn license_text(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let license = v3::tags::license_text(params)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert to V2 format
|
||||
Ok(
|
||||
match v2_reroute::extract_ok_json::<v3::tags::LicenseText>(license).await {
|
||||
match v2_reroute::extract_ok_json::<v3::tags::LicenseText>(license)
|
||||
.await
|
||||
{
|
||||
Ok(license) => HttpResponse::Ok().json(LicenseText {
|
||||
title: license.title,
|
||||
body: license.body,
|
||||
@@ -244,7 +258,11 @@ pub async fn donation_platform_list(
|
||||
|
||||
// Convert to V2 format
|
||||
Ok(
|
||||
match v2_reroute::extract_ok_json::<Vec<LinkPlatformQueryData>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<Vec<LinkPlatformQueryData>>(
|
||||
response,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(platforms) => {
|
||||
let platforms = platforms
|
||||
.into_iter()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId, TeamMember};
|
||||
use crate::models::teams::{
|
||||
OrganizationPermissions, ProjectPermissions, TeamId, TeamMember,
|
||||
};
|
||||
use crate::models::users::UserId;
|
||||
use crate::models::v2::teams::LegacyTeamMember;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -36,9 +38,15 @@ pub async fn team_members_get_project(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::teams::team_members_get_project(req, info, pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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) => {
|
||||
@@ -61,9 +69,10 @@ pub async fn team_members_get(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::teams::team_members_get(req, info, pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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) => {
|
||||
|
||||
@@ -110,7 +110,14 @@ pub async fn message_delete(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so we don't need to convert the response
|
||||
v3::threads::message_delete(req, info, pool, redis, session_queue, file_host)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
v3::threads::message_delete(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
session_queue,
|
||||
file_host,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
@@ -64,15 +64,19 @@ pub async fn users_get(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response =
|
||||
v3::users::users_get(web::Query(v3::users::UserIds { ids: ids.ids }), pool, redis)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::users::users_get(
|
||||
web::Query(v3::users::UserIds { ids: ids.ids }),
|
||||
pool,
|
||||
redis,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Vec<User>>(response).await {
|
||||
Ok(users) => {
|
||||
let legacy_users: Vec<LegacyUser> = users.into_iter().map(LegacyUser::from).collect();
|
||||
let legacy_users: Vec<LegacyUser> =
|
||||
users.into_iter().map(LegacyUser::from).collect();
|
||||
Ok(HttpResponse::Ok().json(legacy_users))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -107,14 +111,21 @@ 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
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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 {
|
||||
Ok(project) => {
|
||||
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(legacy_projects))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -230,14 +241,21 @@ pub async fn user_follows(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::users::user_follows(req, info, pool.clone(), redis.clone(), session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::users::user_follows(
|
||||
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 {
|
||||
Ok(project) => {
|
||||
let legacy_projects = LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(project, &**pool, &redis).await?;
|
||||
Ok(HttpResponse::Ok().json(legacy_projects))
|
||||
}
|
||||
Err(response) => Ok(response),
|
||||
@@ -252,9 +270,10 @@ pub async fn user_notifications(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::users::user_notifications(req, info, pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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) => {
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::projects::{
|
||||
Dependency, FileType, Loader, ProjectId, Version, VersionId, VersionStatus, VersionType,
|
||||
Dependency, FileType, Loader, ProjectId, Version, VersionId, VersionStatus,
|
||||
VersionType,
|
||||
};
|
||||
use crate::models::v2::projects::LegacyVersion;
|
||||
use crate::queue::moderation::AutomatedModerationQueue;
|
||||
@@ -93,7 +94,8 @@ pub async fn version_create(
|
||||
let payload = v2_reroute::alter_actix_multipart(
|
||||
payload,
|
||||
req.headers().clone(),
|
||||
|legacy_create: InitialVersionData, content_dispositions: Vec<ContentDisposition>| {
|
||||
|legacy_create: InitialVersionData,
|
||||
content_dispositions: Vec<ContentDisposition>| {
|
||||
let client = client.clone();
|
||||
let redis = redis.clone();
|
||||
async move {
|
||||
@@ -105,19 +107,27 @@ pub async fn version_create(
|
||||
);
|
||||
|
||||
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply singleplayer, etc.
|
||||
let loaders = match v3::tags::loader_list(client.clone(), redis.clone()).await {
|
||||
Ok(loader_response) => {
|
||||
(v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(loader_response)
|
||||
let loaders =
|
||||
match v3::tags::loader_list(client.clone(), redis.clone())
|
||||
.await
|
||||
{
|
||||
Ok(loader_response) => {
|
||||
(v2_reroute::extract_ok_json::<
|
||||
Vec<v3::tags::LoaderData>,
|
||||
>(loader_response)
|
||||
.await)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
Err(_) => vec![],
|
||||
};
|
||||
.unwrap_or_default()
|
||||
}
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
let loader_fields_aggregate = loaders
|
||||
.into_iter()
|
||||
.filter_map(|loader| {
|
||||
if legacy_create.loaders.contains(&Loader(loader.name.clone())) {
|
||||
if legacy_create
|
||||
.loaders
|
||||
.contains(&Loader(loader.name.clone()))
|
||||
{
|
||||
Some(loader.supported_fields)
|
||||
} else {
|
||||
None
|
||||
@@ -150,15 +160,29 @@ pub async fn version_create(
|
||||
.map(|f| (f.to_string(), json!(false))),
|
||||
);
|
||||
if let Some(example_version_fields) =
|
||||
get_example_version_fields(legacy_create.project_id, client, &redis).await?
|
||||
get_example_version_fields(
|
||||
legacy_create.project_id,
|
||||
client,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
fields.extend(example_version_fields.into_iter().filter_map(|f| {
|
||||
if side_type_loader_field_names.contains(&f.field_name.as_str()) {
|
||||
Some((f.field_name, f.value.serialize_internal()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
fields.extend(
|
||||
example_version_fields.into_iter().filter_map(
|
||||
|f| {
|
||||
if side_type_loader_field_names
|
||||
.contains(&f.field_name.as_str())
|
||||
{
|
||||
Some((
|
||||
f.field_name,
|
||||
f.value.serialize_internal(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Handle project type via file extension prediction
|
||||
@@ -180,9 +204,14 @@ pub async fn version_create(
|
||||
// Similarly, check actual content disposition for mrpacks, in case file_parts is wrong
|
||||
for content_disposition in content_dispositions {
|
||||
// Uses version_create functions to get the file name and extension
|
||||
let (_, file_extension) = version_creation::get_name_ext(&content_disposition)?;
|
||||
let (_, file_extension) =
|
||||
version_creation::get_name_ext(&content_disposition)?;
|
||||
crate::util::ext::project_file_type(file_extension)
|
||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidFileType(
|
||||
file_extension.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if file_extension == "mrpack" {
|
||||
project_type = Some("modpack");
|
||||
@@ -193,7 +222,10 @@ pub async fn version_create(
|
||||
// Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
|
||||
// Setting of 'project_type' directly is removed, it's loader-based now.
|
||||
if project_type == Some("modpack") {
|
||||
fields.insert("mrpack_loaders".to_string(), json!(legacy_create.loaders));
|
||||
fields.insert(
|
||||
"mrpack_loaders".to_string(),
|
||||
json!(legacy_create.loaders),
|
||||
);
|
||||
}
|
||||
|
||||
let loaders = if project_type == Some("modpack") {
|
||||
@@ -257,18 +289,20 @@ async fn get_example_version_fields(
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let vid = match project_item::Project::get_id(project_id.into(), &**pool, redis)
|
||||
.await?
|
||||
.and_then(|p| p.versions.first().cloned())
|
||||
{
|
||||
Some(vid) => vid,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let vid =
|
||||
match project_item::Project::get_id(project_id.into(), &**pool, redis)
|
||||
.await?
|
||||
.and_then(|p| p.versions.first().cloned())
|
||||
{
|
||||
Some(vid) => vid,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let example_version = match version_item::Version::get(vid, &**pool, redis).await? {
|
||||
Some(version) => version,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let example_version =
|
||||
match version_item::Version::get(vid, &**pool, redis).await? {
|
||||
Some(version) => version,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(Some(example_version.version_fields))
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,16 @@ pub async fn get_version_from_hash(
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response =
|
||||
v3::version_file::get_version_from_hash(req, info, pool, redis, hash_query, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::version_file::get_version_from_hash(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
hash_query,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<Version>(response).await {
|
||||
@@ -64,9 +70,16 @@ pub async fn download_version(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns TemporaryRedirect, so no need to convert to V2
|
||||
v3::version_file::download_version(req, info, pool, redis, hash_query, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
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}
|
||||
@@ -80,9 +93,16 @@ pub async fn delete_file(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so no need to convert to V2
|
||||
v3::version_file::delete_file(req, info, pool, redis, hash_query, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
v3::version_file::delete_file(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
hash_query,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -171,7 +191,9 @@ pub async fn get_versions_from_hashes(
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert to V2
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(versions) => {
|
||||
let v2_versions = versions
|
||||
.into_iter()
|
||||
@@ -210,7 +232,9 @@ pub async fn get_projects_from_hashes(
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert to V2
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Project>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Project>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(projects_hashes) => {
|
||||
let hash_to_project_id = projects_hashes
|
||||
.iter()
|
||||
@@ -219,14 +243,19 @@ pub async fn get_projects_from_hashes(
|
||||
(hash.clone(), project_id)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let legacy_projects =
|
||||
LegacyProject::from_many(projects_hashes.into_values().collect(), &**pool, &redis)
|
||||
.await?;
|
||||
let legacy_projects = LegacyProject::from_many(
|
||||
projects_hashes.into_values().collect(),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let legacy_projects_hashes = hash_to_project_id
|
||||
.into_iter()
|
||||
.filter_map(|(hash, project_id)| {
|
||||
let legacy_project =
|
||||
legacy_projects.iter().find(|x| x.id == project_id)?.clone();
|
||||
let legacy_project = legacy_projects
|
||||
.iter()
|
||||
.find(|x| x.id == project_id)?
|
||||
.clone();
|
||||
Some((hash, legacy_project))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
@@ -261,12 +290,15 @@ pub async fn update_files(
|
||||
hashes: update_data.hashes,
|
||||
};
|
||||
|
||||
let response = v3::version_file::update_files(pool, redis, web::Json(update_data))
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response =
|
||||
v3::version_file::update_files(pool, redis, web::Json(update_data))
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(returned_versions) => {
|
||||
let v3_versions = returned_versions
|
||||
.into_iter()
|
||||
@@ -316,7 +348,8 @@ pub async fn update_individual_files(
|
||||
game_versions.push(serde_json::json!(gv.clone()));
|
||||
}
|
||||
if !game_versions.is_empty() {
|
||||
loader_fields.insert("game_versions".to_string(), game_versions);
|
||||
loader_fields
|
||||
.insert("game_versions".to_string(), game_versions);
|
||||
}
|
||||
v3::version_file::FileUpdateData {
|
||||
hash: x.hash.clone(),
|
||||
@@ -339,7 +372,9 @@ pub async fn update_individual_files(
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
|
||||
// Convert response to V2 format
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response).await {
|
||||
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response)
|
||||
.await
|
||||
{
|
||||
Ok(returned_versions) => {
|
||||
let v3_versions = returned_versions
|
||||
.into_iter()
|
||||
|
||||
@@ -4,7 +4,9 @@ use super::ApiError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models;
|
||||
use crate::models::ids::VersionId;
|
||||
use crate::models::projects::{Dependency, FileType, Version, VersionStatus, VersionType};
|
||||
use crate::models::projects::{
|
||||
Dependency, FileType, Version, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::v2::projects::LegacyVersion;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::{v2_reroute, v3};
|
||||
@@ -67,7 +69,8 @@ pub async fn version_list(
|
||||
for gv in versions {
|
||||
game_versions.push(serde_json::json!(gv.clone()));
|
||||
}
|
||||
loader_fields.insert("game_versions".to_string(), game_versions);
|
||||
loader_fields
|
||||
.insert("game_versions".to_string(), game_versions);
|
||||
|
||||
if let Some(ref loaders) = loaders {
|
||||
loader_fields.insert(
|
||||
@@ -94,10 +97,16 @@ pub async fn version_list(
|
||||
offset: filters.offset,
|
||||
};
|
||||
|
||||
let response =
|
||||
v3::versions::version_list(req, info, web::Query(filters), pool, redis, session_queue)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let response = v3::versions::version_list(
|
||||
req,
|
||||
info,
|
||||
web::Query(filters),
|
||||
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 {
|
||||
@@ -122,9 +131,15 @@ 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
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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) => {
|
||||
@@ -149,9 +164,15 @@ 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
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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 {
|
||||
@@ -175,9 +196,10 @@ 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
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
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) => {
|
||||
@@ -252,16 +274,19 @@ pub async fn version_edit(
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)?;
|
||||
let old_version = match v2_reroute::extract_ok_json::<Version>(old_version).await {
|
||||
Ok(version) => version,
|
||||
Err(response) => return Ok(response),
|
||||
};
|
||||
let old_version =
|
||||
match v2_reroute::extract_ok_json::<Version>(old_version).await {
|
||||
Ok(version) => version,
|
||||
Err(response) => return Ok(response),
|
||||
};
|
||||
|
||||
// If this has 'mrpack_loaders' as a loader field previously, this is a modpack.
|
||||
// Therefore, if we are modifying the 'loader' field in this case,
|
||||
// we are actually modifying the 'mrpack_loaders' loader field
|
||||
let mut loaders = new_version.loaders.clone();
|
||||
if old_version.fields.contains_key("mrpack_loaders") && new_version.loaders.is_some() {
|
||||
if old_version.fields.contains_key("mrpack_loaders")
|
||||
&& new_version.loaders.is_some()
|
||||
{
|
||||
fields.insert(
|
||||
"mrpack_loaders".to_string(),
|
||||
serde_json::json!(new_version.loaders),
|
||||
@@ -315,7 +340,14 @@ pub async fn version_delete(
|
||||
search_config: web::Data<SearchConfig>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
// Returns NoContent, so we don't need to convert the response
|
||||
v3::versions::version_delete(req, info, pool, redis, session_queue, search_config)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
v3::versions::version_delete(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
session_queue,
|
||||
search_config,
|
||||
)
|
||||
.await
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,20 @@ 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 crate::util::actix::{
|
||||
generate_multipart, MultipartSegment, MultipartSegmentData,
|
||||
};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::http::header::{ContentDisposition, HeaderMap, TryIntoHeaderPair};
|
||||
use actix_web::http::header::{
|
||||
ContentDisposition, HeaderMap, TryIntoHeaderPair,
|
||||
};
|
||||
use actix_web::HttpResponse;
|
||||
use futures::{stream, Future, StreamExt};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub async fn extract_ok_json<T>(response: HttpResponse) -> Result<T, HttpResponse>
|
||||
pub async fn extract_ok_json<T>(
|
||||
response: HttpResponse,
|
||||
) -> Result<T, HttpResponse>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
@@ -27,7 +33,8 @@ where
|
||||
let bytes = actix_web::body::to_bytes(body)
|
||||
.await
|
||||
.map_err(|_| failure_http_response())?;
|
||||
let json_value: T = serde_json::from_slice(&bytes).map_err(|_| failure_http_response())?;
|
||||
let json_value: T = serde_json::from_slice(&bytes)
|
||||
.map_err(|_| failure_http_response())?;
|
||||
Ok(json_value)
|
||||
} else {
|
||||
Err(response)
|
||||
@@ -119,9 +126,10 @@ where
|
||||
let json_value = json.ok_or(CreateError::InvalidInput(
|
||||
"No json segment found in multipart.".to_string(),
|
||||
))?;
|
||||
let mut json_segment = json_segment.ok_or(CreateError::InvalidInput(
|
||||
"No json segment found in multipart.".to_string(),
|
||||
))?;
|
||||
let mut json_segment =
|
||||
json_segment.ok_or(CreateError::InvalidInput(
|
||||
"No json segment found in multipart.".to_string(),
|
||||
))?;
|
||||
|
||||
// Call closure, with the json value and names of the other segments
|
||||
let json_value: U = closure(json_value, content_dispositions).await?;
|
||||
@@ -144,11 +152,15 @@ where
|
||||
headers.insert(key, value);
|
||||
}
|
||||
Err(err) => {
|
||||
CreateError::InvalidInput(format!("Error inserting test header: {:?}.", err));
|
||||
CreateError::InvalidInput(format!(
|
||||
"Error inserting test header: {:?}.",
|
||||
err
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let new_multipart = Multipart::new(&headers, stream::once(async { Ok(payload) }));
|
||||
let new_multipart =
|
||||
Multipart::new(&headers, stream::once(async { Ok(payload) }));
|
||||
|
||||
Ok(new_multipart)
|
||||
}
|
||||
@@ -165,10 +177,10 @@ pub fn convert_side_types_v3(
|
||||
|| server_side == Required
|
||||
|| server_side == Optional;
|
||||
let client_and_server = singleplayer;
|
||||
let client_only =
|
||||
(client_side == Required || client_side == Optional) && server_side != Required;
|
||||
let server_only =
|
||||
(server_side == Required || server_side == Optional) && client_side != Required;
|
||||
let client_only = (client_side == Required || client_side == Optional)
|
||||
&& server_side != Required;
|
||||
let server_only = (server_side == Required || server_side == Optional)
|
||||
&& client_side != Required;
|
||||
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert("singleplayer".to_string(), json!(singleplayer));
|
||||
@@ -181,7 +193,9 @@ pub fn convert_side_types_v3(
|
||||
// Converts plugin loaders from v2 to v3, for search facets
|
||||
// Within every 1st and 2nd level (the ones allowed in v2), we convert every instance of:
|
||||
// "project_type:mod" to "project_type:plugin" OR "project_type:mod"
|
||||
pub fn convert_plugin_loader_facets_v3(facets: Vec<Vec<String>>) -> Vec<Vec<String>> {
|
||||
pub fn convert_plugin_loader_facets_v3(
|
||||
facets: Vec<Vec<String>>,
|
||||
) -> Vec<Vec<String>> {
|
||||
facets
|
||||
.into_iter()
|
||||
.map(|inner_facets| {
|
||||
@@ -246,7 +260,8 @@ pub fn convert_side_types_v2_bools(
|
||||
Some("shader") => (Required, Unsupported),
|
||||
Some("resourcepack") => (Required, Unsupported),
|
||||
_ => {
|
||||
let singleplayer = singleplayer.or(client_and_server).unwrap_or(false);
|
||||
let singleplayer =
|
||||
singleplayer.or(client_and_server).unwrap_or(false);
|
||||
|
||||
match (singleplayer, client_only, server_only) {
|
||||
// Only singleplayer
|
||||
@@ -282,7 +297,9 @@ pub fn capitalize_first(input: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::models::v2::projects::LegacySideType::{Optional, Required, Unsupported};
|
||||
use crate::models::v2::projects::LegacySideType::{
|
||||
Optional, Required, Unsupported,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn convert_types() {
|
||||
@@ -300,8 +317,10 @@ mod tests {
|
||||
if lossy_pairs.contains(&(client_side, server_side)) {
|
||||
continue;
|
||||
}
|
||||
let side_types = convert_side_types_v3(client_side, server_side);
|
||||
let (client_side2, server_side2) = convert_side_types_v2(&side_types, None);
|
||||
let side_types =
|
||||
convert_side_types_v3(client_side, server_side);
|
||||
let (client_side2, server_side2) =
|
||||
convert_side_types_v2(&side_types, None);
|
||||
assert_eq!(client_side, client_side2);
|
||||
assert_eq!(server_side, server_side2);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,8 @@ pub async fn playtimes_get(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids = filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
|
||||
// Get the views
|
||||
let playtimes = crate::clickhouse::fetch_playtimes(
|
||||
@@ -164,7 +165,8 @@ pub async fn views_get(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids = filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
|
||||
// Get the views
|
||||
let views = crate::clickhouse::fetch_views(
|
||||
@@ -230,7 +232,9 @@ pub async fn downloads_get(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids = filter_allowed_ids(project_ids, user_option, &pool, &redis, None).await?;
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user_option, &pool, &redis, None)
|
||||
.await?;
|
||||
|
||||
// Get the downloads
|
||||
let downloads = crate::clickhouse::fetch_downloads(
|
||||
@@ -299,17 +303,26 @@ pub async fn revenue_get(
|
||||
|
||||
// Round end_date up to nearest resolution
|
||||
let diff = end_date.timestamp() % (resolution_minutes as i64 * 60);
|
||||
let end_date = end_date + Duration::seconds((resolution_minutes as i64 * 60) - diff);
|
||||
let end_date =
|
||||
end_date + Duration::seconds((resolution_minutes as i64 * 60) - diff);
|
||||
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user.clone(), &pool, &redis, Some(true)).await?;
|
||||
let project_ids = filter_allowed_ids(
|
||||
project_ids,
|
||||
user.clone(),
|
||||
&pool,
|
||||
&redis,
|
||||
Some(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let duration: PgInterval = Duration::minutes(resolution_minutes as i64)
|
||||
.try_into()
|
||||
.map_err(|_| ApiError::InvalidInput("Invalid resolution_minutes".to_string()))?;
|
||||
.map_err(|_| {
|
||||
ApiError::InvalidInput("Invalid resolution_minutes".to_string())
|
||||
})?;
|
||||
// Get the revenue data
|
||||
let project_ids = project_ids.unwrap_or_default();
|
||||
|
||||
@@ -424,7 +437,8 @@ pub async fn countries_downloads_get(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids = filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
|
||||
// Get the countries
|
||||
let countries = crate::clickhouse::fetch_countries_downloads(
|
||||
@@ -496,7 +510,8 @@ pub async fn countries_views_get(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
// - If no project_ids or version_ids are provided, we default to all projects the user has access to
|
||||
let project_ids = filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
let project_ids =
|
||||
filter_allowed_ids(project_ids, user, &pool, &redis, None).await?;
|
||||
|
||||
// Get the countries
|
||||
let countries = crate::clickhouse::fetch_countries_views(
|
||||
@@ -564,55 +579,68 @@ async fn filter_allowed_ids(
|
||||
// Convert String list to list of ProjectIds or VersionIds
|
||||
// - Filter out unauthorized projects/versions
|
||||
let project_ids = if let Some(project_strings) = project_ids {
|
||||
let projects_data =
|
||||
database::models::Project::get_many(&project_strings, &***pool, redis).await?;
|
||||
let projects_data = database::models::Project::get_many(
|
||||
&project_strings,
|
||||
&***pool,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let team_ids = projects_data
|
||||
.iter()
|
||||
.map(|x| x.inner.team_id)
|
||||
.collect::<Vec<database::models::TeamId>>();
|
||||
let team_members =
|
||||
database::models::TeamMember::get_from_team_full_many(&team_ids, &***pool, redis)
|
||||
.await?;
|
||||
database::models::TeamMember::get_from_team_full_many(
|
||||
&team_ids, &***pool, redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_ids = projects_data
|
||||
.iter()
|
||||
.filter_map(|x| x.inner.organization_id)
|
||||
.collect::<Vec<database::models::OrganizationId>>();
|
||||
let organizations =
|
||||
database::models::Organization::get_many_ids(&organization_ids, &***pool, redis)
|
||||
.await?;
|
||||
|
||||
let organization_team_ids = organizations
|
||||
.iter()
|
||||
.map(|x| x.team_id)
|
||||
.collect::<Vec<database::models::TeamId>>();
|
||||
let organization_team_members = database::models::TeamMember::get_from_team_full_many(
|
||||
&organization_team_ids,
|
||||
let organizations = database::models::Organization::get_many_ids(
|
||||
&organization_ids,
|
||||
&***pool,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_team_ids = organizations
|
||||
.iter()
|
||||
.map(|x| x.team_id)
|
||||
.collect::<Vec<database::models::TeamId>>();
|
||||
let organization_team_members =
|
||||
database::models::TeamMember::get_from_team_full_many(
|
||||
&organization_team_ids,
|
||||
&***pool,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ids = projects_data
|
||||
.into_iter()
|
||||
.filter(|project| {
|
||||
let team_member = team_members
|
||||
.iter()
|
||||
.find(|x| x.team_id == project.inner.team_id && x.user_id == user.id.into());
|
||||
let team_member = team_members.iter().find(|x| {
|
||||
x.team_id == project.inner.team_id
|
||||
&& x.user_id == user.id.into()
|
||||
});
|
||||
|
||||
let organization = project
|
||||
.inner
|
||||
.organization_id
|
||||
.and_then(|oid| organizations.iter().find(|x| x.id == oid));
|
||||
|
||||
let organization_team_member = if let Some(organization) = organization {
|
||||
organization_team_members
|
||||
.iter()
|
||||
.find(|x| x.team_id == organization.team_id && x.user_id == user.id.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let organization_team_member =
|
||||
if let Some(organization) = organization {
|
||||
organization_team_members.iter().find(|x| {
|
||||
x.team_id == organization.team_id
|
||||
&& x.user_id == user.id.into()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let permissions = ProjectPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::auth::checks::is_visible_collection;
|
||||
use crate::auth::{filter_visible_collections, get_user_from_headers};
|
||||
use crate::database::models::{collection_item, generate_collection_id, project_item};
|
||||
use crate::database::models::{
|
||||
collection_item, generate_collection_id, project_item,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::collections::{Collection, CollectionStatus};
|
||||
@@ -74,13 +76,14 @@ pub async fn collection_create(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
collection_create_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
collection_create_data.validate().map_err(|err| {
|
||||
CreateError::InvalidInput(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let mut transaction = client.begin().await?;
|
||||
|
||||
let collection_id: CollectionId = generate_collection_id(&mut transaction).await?.into();
|
||||
let collection_id: CollectionId =
|
||||
generate_collection_id(&mut transaction).await?.into();
|
||||
|
||||
let initial_project_ids = project_item::Project::get_many(
|
||||
&collection_create_data.projects,
|
||||
@@ -140,10 +143,13 @@ pub async fn collections_get(
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
let ids = ids
|
||||
.into_iter()
|
||||
.map(|x| parse_base62(x).map(|x| database::models::CollectionId(x as i64)))
|
||||
.map(|x| {
|
||||
parse_base62(x).map(|x| database::models::CollectionId(x as i64))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let collections_data = database::models::Collection::get_many(&ids, &**pool, &redis).await?;
|
||||
let collections_data =
|
||||
database::models::Collection::get_many(&ids, &**pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -156,7 +162,8 @@ pub async fn collections_get(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
let collections = filter_visible_collections(collections_data, &user_option).await?;
|
||||
let collections =
|
||||
filter_visible_collections(collections_data, &user_option).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(collections))
|
||||
}
|
||||
@@ -171,7 +178,8 @@ pub async fn collection_get(
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
let collection_data = database::models::Collection::get(id, &**pool, &redis).await?;
|
||||
let collection_data =
|
||||
database::models::Collection::get(id, &**pool, &redis).await?;
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
@@ -228,9 +236,9 @@ pub async fn collection_edit(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_collection
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
new_collection.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
@@ -275,7 +283,8 @@ pub async fn collection_edit(
|
||||
|
||||
if let Some(status) = &new_collection.status {
|
||||
if !(user.role.is_mod()
|
||||
|| collection_item.status.is_approved() && status.can_be_requested())
|
||||
|| collection_item.status.is_approved()
|
||||
&& status.can_be_requested())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set this status!".to_string(),
|
||||
@@ -313,13 +322,14 @@ pub async fn collection_edit(
|
||||
.collect_vec();
|
||||
let mut validated_project_ids = Vec::new();
|
||||
for project_id in new_project_ids {
|
||||
let project = database::models::Project::get(project_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
let project =
|
||||
database::models::Project::get(project_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"The specified project {project_id} does not exist!"
|
||||
))
|
||||
})?;
|
||||
})?;
|
||||
validated_project_ids.push(project.inner.id.0);
|
||||
}
|
||||
// Insert- don't throw an error if it already exists
|
||||
@@ -348,7 +358,8 @@ pub async fn collection_edit(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -384,11 +395,14 @@ pub async fn collection_icon_edit(
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
let collection_item = database::models::Collection::get(id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
})?;
|
||||
let collection_item =
|
||||
database::models::Collection::get(id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified collection does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !can_modify_collection(&collection_item, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
@@ -401,8 +415,12 @@ pub async fn collection_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let collection_id: CollectionId = collection_item.id.into();
|
||||
let upload_result = crate::util::img::upload_image_optimized(
|
||||
@@ -432,7 +450,8 @@ pub async fn collection_icon_edit(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -457,11 +476,14 @@ pub async fn delete_collection_icon(
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let id = database::models::CollectionId(parse_base62(&string)? as i64);
|
||||
let collection_item = database::models::Collection::get(id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
})?;
|
||||
let collection_item =
|
||||
database::models::Collection::get(id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified collection does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
if !can_modify_collection(&collection_item, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
@@ -486,7 +508,8 @@ pub async fn delete_collection_icon(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -513,15 +536,21 @@ pub async fn collection_delete(
|
||||
let collection = database::models::Collection::get(id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified collection does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified collection does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
if !can_modify_collection(&collection, &user) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
}
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let result =
|
||||
database::models::Collection::remove(collection.id, &mut transaction, &redis).await?;
|
||||
let result = database::models::Collection::remove(
|
||||
collection.id,
|
||||
&mut transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection.id, &redis).await?;
|
||||
|
||||
@@ -4,7 +4,9 @@ use super::threads::is_authorized_thread;
|
||||
use crate::auth::checks::{is_team_member_project, is_team_member_version};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::models::{project_item, report_item, thread_item, version_item};
|
||||
use crate::database::models::{
|
||||
project_item, report_item, thread_item, version_item,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::{ThreadMessageId, VersionId};
|
||||
@@ -50,18 +52,31 @@ pub async fn images_add(
|
||||
|
||||
let scopes = vec![context.relevant_scope()];
|
||||
|
||||
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue, Some(&scopes))
|
||||
.await?
|
||||
.1;
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&scopes),
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
// Attempt to associated a supplied id with the context
|
||||
// If the context cannot be found, or the user is not authorized to upload images for the context, return an error
|
||||
match &mut context {
|
||||
ImageContext::Project { project_id } => {
|
||||
if let Some(id) = data.project_id {
|
||||
let project = project_item::Project::get(&id, &**pool, &redis).await?;
|
||||
let project =
|
||||
project_item::Project::get(&id, &**pool, &redis).await?;
|
||||
if let Some(project) = project {
|
||||
if is_team_member_project(&project.inner, &Some(user.clone()), &pool).await? {
|
||||
if is_team_member_project(
|
||||
&project.inner,
|
||||
&Some(user.clone()),
|
||||
&pool,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
*project_id = Some(project.inner.id.into());
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
@@ -77,10 +92,17 @@ pub async fn images_add(
|
||||
}
|
||||
ImageContext::Version { version_id } => {
|
||||
if let Some(id) = data.version_id {
|
||||
let version = version_item::Version::get(id.into(), &**pool, &redis).await?;
|
||||
let version =
|
||||
version_item::Version::get(id.into(), &**pool, &redis)
|
||||
.await?;
|
||||
if let Some(version) = version {
|
||||
if is_team_member_version(&version.inner, &Some(user.clone()), &pool, &redis)
|
||||
.await?
|
||||
if is_team_member_version(
|
||||
&version.inner,
|
||||
&Some(user.clone()),
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
*version_id = Some(version.inner.id.into());
|
||||
} else {
|
||||
@@ -97,11 +119,15 @@ pub async fn images_add(
|
||||
}
|
||||
ImageContext::ThreadMessage { thread_message_id } => {
|
||||
if let Some(id) = data.thread_message_id {
|
||||
let thread_message = thread_item::ThreadMessage::get(id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The thread message could not found.".to_string())
|
||||
})?;
|
||||
let thread_message =
|
||||
thread_item::ThreadMessage::get(id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The thread message could not found."
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
let thread = thread_item::Thread::get(thread_message.thread_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
@@ -125,7 +151,9 @@ pub async fn images_add(
|
||||
let report = report_item::Report::get(id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The report could not be found.".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The report could not be found.".to_string(),
|
||||
)
|
||||
})?;
|
||||
let thread = thread_item::Thread::get(report.thread_id, &**pool)
|
||||
.await?
|
||||
@@ -151,8 +179,12 @@ pub async fn images_add(
|
||||
}
|
||||
|
||||
// Upload the image to the file host
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 1_048_576, "Icons must be smaller than 1MiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
1_048_576,
|
||||
"Icons must be smaller than 1MiB",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let content_length = bytes.len();
|
||||
let upload_result = upload_image_optimized(
|
||||
|
||||
@@ -55,8 +55,11 @@ pub async fn notifications_get(
|
||||
.collect();
|
||||
|
||||
let notifications_data: Vec<DBNotification> =
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get_many(
|
||||
¬ification_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let notifications: Vec<Notification> = notifications_data
|
||||
.into_iter()
|
||||
@@ -87,7 +90,11 @@ pub async fn notification_get(
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
database::models::notification_item::Notification::get(
|
||||
id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if user.id == data.user_id.into() || user.role.is_admin() {
|
||||
@@ -120,7 +127,11 @@ pub async fn notification_read(
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
database::models::notification_item::Notification::get(
|
||||
id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||
@@ -166,7 +177,11 @@ pub async fn notification_delete(
|
||||
let id = info.into_inner().0;
|
||||
|
||||
let notification_data =
|
||||
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||
database::models::notification_item::Notification::get(
|
||||
id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||
@@ -184,7 +199,8 @@ pub async fn notification_delete(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
"You are not authorized to delete this notification!".to_string(),
|
||||
"You are not authorized to delete this notification!"
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
@@ -209,18 +225,23 @@ pub async fn notifications_read(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
let notification_ids =
|
||||
serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let notifications_data =
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get_many(
|
||||
¬ification_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||
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_admin() {
|
||||
@@ -257,18 +278,23 @@ pub async fn notifications_delete(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
let notification_ids =
|
||||
serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let notifications_data =
|
||||
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||
.await?;
|
||||
database::models::notification_item::Notification::get_many(
|
||||
¬ification_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||
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_admin() {
|
||||
|
||||
@@ -36,7 +36,10 @@ use crate::{
|
||||
};
|
||||
use crate::{
|
||||
file_hosting::FileHost,
|
||||
models::{ids::base62_impl::parse_base62, oauth_clients::DeleteOAuthClientQueryParam},
|
||||
models::{
|
||||
ids::base62_impl::parse_base62,
|
||||
oauth_clients::DeleteOAuthClientQueryParam,
|
||||
},
|
||||
util::routes::read_from_payload,
|
||||
};
|
||||
|
||||
@@ -80,13 +83,16 @@ pub async fn get_user_clients(
|
||||
let target_user = User::get(&info.into_inner(), &**pool, &redis).await?;
|
||||
|
||||
if let Some(target_user) = target_user {
|
||||
if target_user.id != current_user.id.into() && !current_user.role.is_admin() {
|
||||
if target_user.id != current_user.id.into()
|
||||
&& !current_user.role.is_admin()
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to see the OAuth clients of this user!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let clients = OAuthClient::get_all_user_clients(target_user.id, &**pool).await?;
|
||||
let clients =
|
||||
OAuthClient::get_all_user_clients(target_user.id, &**pool).await?;
|
||||
|
||||
let response = clients
|
||||
.into_iter()
|
||||
@@ -136,7 +142,9 @@ pub struct NewOAuthApp {
|
||||
)]
|
||||
pub name: String,
|
||||
|
||||
#[validate(custom(function = "crate::util::validate::validate_no_restricted_scopes"))]
|
||||
#[validate(custom(
|
||||
function = "crate::util::validate::validate_no_restricted_scopes"
|
||||
))]
|
||||
pub max_scopes: Scopes,
|
||||
|
||||
pub redirect_uris: Vec<String>,
|
||||
@@ -169,9 +177,9 @@ pub async fn oauth_client_create<'a>(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_oauth_app
|
||||
.validate()
|
||||
.map_err(|e| CreateError::ValidationError(validation_errors_to_string(e, None)))?;
|
||||
new_oauth_app.validate().map_err(|e| {
|
||||
CreateError::ValidationError(validation_errors_to_string(e, None))
|
||||
})?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@@ -180,8 +188,12 @@ pub async fn oauth_client_create<'a>(
|
||||
let client_secret = generate_oauth_client_secret();
|
||||
let client_secret_hash = DBOAuthClient::hash_secret(&client_secret);
|
||||
|
||||
let redirect_uris =
|
||||
create_redirect_uris(&new_oauth_app.redirect_uris, client_id, &mut transaction).await?;
|
||||
let redirect_uris = create_redirect_uris(
|
||||
&new_oauth_app.redirect_uris,
|
||||
client_id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client = OAuthClient {
|
||||
id: client_id,
|
||||
@@ -226,7 +238,8 @@ pub async fn oauth_client_delete<'a>(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let client = OAuthClient::get(client_id.into_inner().into(), &**pool).await?;
|
||||
let client =
|
||||
OAuthClient::get(client_id.into_inner().into(), &**pool).await?;
|
||||
if let Some(client) = client {
|
||||
client.validate_authorized(Some(¤t_user))?;
|
||||
OAuthClient::remove(client.id, &**pool).await?;
|
||||
@@ -245,7 +258,9 @@ pub struct OAuthClientEdit {
|
||||
)]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[validate(custom(function = "crate::util::validate::validate_no_restricted_scopes"))]
|
||||
#[validate(custom(
|
||||
function = "crate::util::validate::validate_no_restricted_scopes"
|
||||
))]
|
||||
pub max_scopes: Option<Scopes>,
|
||||
|
||||
#[validate(length(min = 1))]
|
||||
@@ -280,11 +295,13 @@ pub async fn oauth_client_edit(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
client_updates
|
||||
.validate()
|
||||
.map_err(|e| ApiError::Validation(validation_errors_to_string(e, None)))?;
|
||||
client_updates.validate().map_err(|e| {
|
||||
ApiError::Validation(validation_errors_to_string(e, None))
|
||||
})?;
|
||||
|
||||
if let Some(existing_client) = OAuthClient::get(client_id.into_inner().into(), &**pool).await? {
|
||||
if let Some(existing_client) =
|
||||
OAuthClient::get(client_id.into_inner().into(), &**pool).await?
|
||||
{
|
||||
existing_client.validate_authorized(Some(¤t_user))?;
|
||||
|
||||
let mut updated_client = existing_client.clone();
|
||||
@@ -317,7 +334,8 @@ pub async fn oauth_client_edit(
|
||||
.await?;
|
||||
|
||||
if let Some(redirects) = redirect_uris {
|
||||
edit_redirects(redirects, &existing_client, &mut transaction).await?;
|
||||
edit_redirects(redirects, &existing_client, &mut transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
@@ -358,7 +376,9 @@ pub async fn oauth_client_icon_edit(
|
||||
let client = OAuthClient::get((*client_id).into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified client does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified client does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
client.validate_authorized(Some(&user))?;
|
||||
@@ -370,8 +390,12 @@ pub async fn oauth_client_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
let upload_result = upload_image_optimized(
|
||||
&format!("data/{}", client_id),
|
||||
bytes.freeze(),
|
||||
@@ -419,7 +443,9 @@ pub async fn oauth_client_icon_delete(
|
||||
let client = OAuthClient::get((*client_id).into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified client does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified client does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
client.validate_authorized(Some(&user))?;
|
||||
|
||||
@@ -461,8 +487,11 @@ pub async fn get_user_oauth_authorizations(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let authorizations =
|
||||
OAuthClientAuthorization::get_all_for_user(current_user.id.into(), &**pool).await?;
|
||||
let authorizations = OAuthClientAuthorization::get_all_for_user(
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mapped: Vec<models::oauth_clients::OAuthClientAuthorization> =
|
||||
authorizations.into_iter().map(|a| a.into()).collect_vec();
|
||||
@@ -488,8 +517,12 @@ pub async fn revoke_oauth_authorization(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
OAuthClientAuthorization::remove(info.client_id.into(), current_user.id.into(), &**pool)
|
||||
.await?;
|
||||
OAuthClientAuthorization::remove(
|
||||
info.client_id.into(),
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().body(""))
|
||||
}
|
||||
@@ -538,12 +571,16 @@ async fn edit_redirects(
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?;
|
||||
OAuthClient::insert_redirect_uris(&redirects_to_add, &mut **transaction).await?;
|
||||
OAuthClient::insert_redirect_uris(&redirects_to_add, &mut **transaction)
|
||||
.await?;
|
||||
|
||||
let mut redirects_to_remove = existing_client.redirect_uris.clone();
|
||||
redirects_to_remove.retain(|r| !updated_redirects.contains(&r.uri));
|
||||
OAuthClient::remove_redirect_uris(redirects_to_remove.iter().map(|r| r.id), &mut **transaction)
|
||||
.await?;
|
||||
OAuthClient::remove_redirect_uris(
|
||||
redirects_to_remove.iter().map(|r| r.id),
|
||||
&mut **transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use std::sync::Arc;
|
||||
use super::ApiError;
|
||||
use crate::auth::{filter_visible_projects, get_user_from_headers};
|
||||
use crate::database::models::team_item::TeamMember;
|
||||
use crate::database::models::{generate_organization_id, team_item, Organization};
|
||||
use crate::database::models::{
|
||||
generate_organization_id, team_item, Organization,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
@@ -83,10 +85,16 @@ pub async fn organization_projects_get(
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects_data =
|
||||
crate::database::models::Project::get_many_ids(&project_ids, &**pool, &redis).await?;
|
||||
let projects_data = crate::database::models::Project::get_many_ids(
|
||||
&project_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let projects = filter_visible_projects(projects_data, ¤t_user, &pool, true).await?;
|
||||
let projects =
|
||||
filter_visible_projects(projects_data, ¤t_user, &pool, true)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
|
||||
@@ -121,9 +129,9 @@ pub async fn organization_create(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_organization
|
||||
.validate()
|
||||
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
new_organization.validate().map_err(|err| {
|
||||
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@@ -135,7 +143,12 @@ pub async fn organization_create(
|
||||
organization_strings.push(name_organization_id.to_string());
|
||||
}
|
||||
organization_strings.push(new_organization.slug.clone());
|
||||
let results = Organization::get_many(&organization_strings, &mut *transaction, &redis).await?;
|
||||
let results = Organization::get_many(
|
||||
&organization_strings,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
if !results.is_empty() {
|
||||
return Err(CreateError::SlugCollision);
|
||||
}
|
||||
@@ -188,7 +201,8 @@ pub async fn organization_create(
|
||||
));
|
||||
};
|
||||
|
||||
let organization = models::organizations::Organization::from(organization, members_data);
|
||||
let organization =
|
||||
models::organizations::Organization::from(organization, members_data);
|
||||
|
||||
Ok(HttpResponse::Ok().json(organization))
|
||||
}
|
||||
@@ -215,7 +229,9 @@ pub async fn organization_get(
|
||||
|
||||
let organization_data = Organization::get(&id, &**pool, &redis).await?;
|
||||
if let Some(data) = organization_data {
|
||||
let members_data = TeamMember::get_from_team_full(data.team_id, &**pool, &redis).await?;
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(data.team_id, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
@@ -237,17 +253,24 @@ pub async fn organization_get(
|
||||
logged_in
|
||||
|| x.accepted
|
||||
|| user_id
|
||||
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||
.map(|y: crate::database::models::UserId| {
|
||||
y == x.user_id
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let organization = models::organizations::Organization::from(data, team_members);
|
||||
let organization =
|
||||
models::organizations::Organization::from(data, team_members);
|
||||
return Ok(HttpResponse::Ok().json(organization));
|
||||
}
|
||||
Err(ApiError::NotFound)
|
||||
@@ -266,13 +289,15 @@ pub async fn organizations_get(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
let organizations_data = Organization::get_many(&ids, &**pool, &redis).await?;
|
||||
let organizations_data =
|
||||
Organization::get_many(&ids, &**pool, &redis).await?;
|
||||
let team_ids = organizations_data
|
||||
.iter()
|
||||
.map(|x| x.team_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||
let teams_data =
|
||||
TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -316,17 +341,24 @@ pub async fn organizations_get(
|
||||
logged_in
|
||||
|| x.accepted
|
||||
|| user_id
|
||||
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||
.map(|y: crate::database::models::UserId| {
|
||||
y == x.user_id
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let organization = models::organizations::Organization::from(data, team_members);
|
||||
let organization =
|
||||
models::organizations::Organization::from(data, team_members);
|
||||
organizations.push(organization);
|
||||
}
|
||||
|
||||
@@ -364,12 +396,13 @@ pub async fn organizations_edit(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_organization
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
new_organization.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result = database::models::Organization::get(&string, &**pool, &redis).await?;
|
||||
let result =
|
||||
database::models::Organization::get(&string, &**pool, &redis).await?;
|
||||
if let Some(organization_item) = result {
|
||||
let id = organization_item.id;
|
||||
|
||||
@@ -380,8 +413,10 @@ pub async fn organizations_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(&user.role, &team_member);
|
||||
let permissions = OrganizationPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
&team_member,
|
||||
);
|
||||
|
||||
if let Some(perms) = permissions {
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -433,8 +468,10 @@ pub async fn organizations_edit(
|
||||
));
|
||||
}
|
||||
|
||||
let name_organization_id_option: Option<u64> = parse_base62(slug).ok();
|
||||
if let Some(name_organization_id) = name_organization_id_option {
|
||||
let name_organization_id_option: Option<u64> =
|
||||
parse_base62(slug).ok();
|
||||
if let Some(name_organization_id) = name_organization_id_option
|
||||
{
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM organizations WHERE id=$1)
|
||||
@@ -446,7 +483,8 @@ pub async fn organizations_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"slug collides with other organization's id!".to_string(),
|
||||
"slug collides with other organization's id!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -465,7 +503,8 @@ pub async fn organizations_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"slug collides with other organization's id!".to_string(),
|
||||
"slug collides with other organization's id!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -494,7 +533,8 @@ pub async fn organizations_edit(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to edit this organization!".to_string(),
|
||||
"You do not have permission to edit this organization!"
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
@@ -520,32 +560,41 @@ pub async fn organization_delete(
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let organization = database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization =
|
||||
database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(&user.role, &Some(team_member))
|
||||
.unwrap_or_default();
|
||||
let permissions = OrganizationPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
&Some(team_member),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !permissions.contains(OrganizationPermissions::DELETE_ORGANIZATION) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to delete this organization!".to_string(),
|
||||
"You don't have permission to delete this organization!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -582,8 +631,10 @@ pub async fn organization_delete(
|
||||
.await?;
|
||||
|
||||
for organization_project_team in organization_project_teams.iter() {
|
||||
let new_id =
|
||||
crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||
let new_id = crate::database::models::ids::generate_team_member_id(
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
let member = TeamMember {
|
||||
id: new_id,
|
||||
team_id: *organization_project_team,
|
||||
@@ -599,13 +650,21 @@ pub async fn organization_delete(
|
||||
member.insert(&mut transaction).await?;
|
||||
}
|
||||
// Safely remove the organization
|
||||
let result =
|
||||
database::models::Organization::remove(organization.id, &mut transaction, &redis).await?;
|
||||
let result = database::models::Organization::remove(
|
||||
organization.id,
|
||||
&mut transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
database::models::Organization::clear_cache(organization.id, Some(organization.slug), &redis)
|
||||
.await?;
|
||||
database::models::Organization::clear_cache(
|
||||
organization.id,
|
||||
Some(organization.slug),
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
for team_id in organization_project_teams {
|
||||
database::models::TeamMember::clear_cache(team_id, &redis).await?;
|
||||
@@ -641,41 +700,59 @@ pub async fn organization_projects_add(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let organization = database::models::Organization::get(&info, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization =
|
||||
database::models::Organization::get(&info, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let project_item = database::models::Project::get(&project_info.project_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
if project_item.inner.organization_id.is_some() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The specified project is already owned by an organization!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let project_team_member = database::models::TeamMember::get_from_user_id_project(
|
||||
project_item.inner.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("You are not a member of this project!".to_string()))?;
|
||||
let organization_team_member = database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
let project_item = database::models::Project::get(
|
||||
&project_info.project_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("You are not a member of this organization!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
if project_item.inner.organization_id.is_some() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The specified project is already owned by an organization!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let project_team_member =
|
||||
database::models::TeamMember::get_from_user_id_project(
|
||||
project_item.inner.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"You are not a member of this project!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let organization_team_member =
|
||||
database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"You are not a member of this organization!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Require ownership of a project to add it to an organization
|
||||
if !current_user.role.is_admin() && !project_team_member.is_owner {
|
||||
@@ -734,8 +811,16 @@ pub async fn organization_projects_add(
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
database::models::User::clear_project_cache(&[current_user.id.into()], &redis).await?;
|
||||
database::models::TeamMember::clear_cache(project_item.inner.team_id, &redis).await?;
|
||||
database::models::User::clear_project_cache(
|
||||
&[current_user.id.into()],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
database::models::TeamMember::clear_cache(
|
||||
project_item.inner.team_id,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
@@ -745,7 +830,8 @@ pub async fn organization_projects_add(
|
||||
.await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to add projects to this organization!".to_string(),
|
||||
"You do not have permission to add projects to this organization!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
@@ -777,17 +863,23 @@ pub async fn organization_projects_remove(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let organization = database::models::Organization::get(&organization_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization =
|
||||
database::models::Organization::get(&organization_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let project_item = database::models::Project::get(&project_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
let project_item =
|
||||
database::models::Project::get(&project_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !project_item
|
||||
.inner
|
||||
@@ -795,20 +887,24 @@ pub async fn organization_projects_remove(
|
||||
.eq(&Some(organization.id))
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The specified project is not owned by this organization!".to_string(),
|
||||
"The specified project is not owned by this organization!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let organization_team_member = database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("You are not a member of this organization!".to_string())
|
||||
})?;
|
||||
let organization_team_member =
|
||||
database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
current_user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"You are not a member of this organization!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let permissions = OrganizationPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
@@ -826,7 +922,8 @@ pub async fn organization_projects_remove(
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified user is not a member of this organization!".to_string(),
|
||||
"The specified user is not a member of this organization!"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -847,7 +944,10 @@ pub async fn organization_projects_remove(
|
||||
Some(new_owner) => new_owner,
|
||||
None => {
|
||||
let new_id =
|
||||
crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||
crate::database::models::ids::generate_team_member_id(
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
let member = TeamMember {
|
||||
id: new_id,
|
||||
team_id: project_item.inner.team_id,
|
||||
@@ -895,8 +995,16 @@ pub async fn organization_projects_remove(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::User::clear_project_cache(&[current_user.id.into()], &redis).await?;
|
||||
database::models::TeamMember::clear_cache(project_item.inner.team_id, &redis).await?;
|
||||
database::models::User::clear_project_cache(
|
||||
&[current_user.id.into()],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
database::models::TeamMember::clear_cache(
|
||||
project_item.inner.team_id,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
database::models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
@@ -906,7 +1014,8 @@ pub async fn organization_projects_remove(
|
||||
.await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to add projects to this organization!".to_string(),
|
||||
"You do not have permission to add projects to this organization!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
@@ -939,11 +1048,14 @@ pub async fn organization_icon_edit(
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let organization_item = database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization_item =
|
||||
database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -954,13 +1066,16 @@ pub async fn organization_icon_edit(
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
let permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(&user.role, &team_member)
|
||||
.unwrap_or_default();
|
||||
let permissions = OrganizationPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
&team_member,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !permissions.contains(OrganizationPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this organization's icon.".to_string(),
|
||||
"You don't have permission to edit this organization's icon."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -972,8 +1087,12 @@ pub async fn organization_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_id: OrganizationId = organization_item.id.into();
|
||||
let upload_result = crate::util::img::upload_image_optimized(
|
||||
@@ -1032,11 +1151,14 @@ pub async fn delete_organization_icon(
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let organization_item = database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization_item =
|
||||
database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
@@ -1047,13 +1169,16 @@ pub async fn delete_organization_icon(
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
let permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(&user.role, &team_member)
|
||||
.unwrap_or_default();
|
||||
let permissions = OrganizationPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
&team_member,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !permissions.contains(OrganizationPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this organization's icon.".to_string(),
|
||||
"You don't have permission to edit this organization's icon."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,27 +46,37 @@ pub async fn paypal_webhook(
|
||||
.headers()
|
||||
.get("PAYPAL-AUTH-ALGO")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing auth algo".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing auth algo".to_string())
|
||||
})?;
|
||||
let cert_url = req
|
||||
.headers()
|
||||
.get("PAYPAL-CERT-URL")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing cert url".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing cert url".to_string())
|
||||
})?;
|
||||
let transmission_id = req
|
||||
.headers()
|
||||
.get("PAYPAL-TRANSMISSION-ID")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing transmission ID".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing transmission ID".to_string())
|
||||
})?;
|
||||
let transmission_sig = req
|
||||
.headers()
|
||||
.get("PAYPAL-TRANSMISSION-SIG")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing transmission sig".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing transmission sig".to_string())
|
||||
})?;
|
||||
let transmission_time = req
|
||||
.headers()
|
||||
.get("PAYPAL-TRANSMISSION-TIME")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing transmission time".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing transmission time".to_string())
|
||||
})?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WebHookResponse {
|
||||
@@ -190,11 +200,14 @@ pub async fn tremendous_webhook(
|
||||
.get("Tremendous-Webhook-Signature")
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.and_then(|x| x.split('=').next_back())
|
||||
.ok_or_else(|| ApiError::InvalidInput("missing webhook signature".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("missing webhook signature".to_string())
|
||||
})?;
|
||||
|
||||
let mut mac: Hmac<Sha256> =
|
||||
Hmac::new_from_slice(dotenvy::var("TREMENDOUS_PRIVATE_KEY")?.as_bytes())
|
||||
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
||||
let mut mac: Hmac<Sha256> = Hmac::new_from_slice(
|
||||
dotenvy::var("TREMENDOUS_PRIVATE_KEY")?.as_bytes(),
|
||||
)
|
||||
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
||||
mac.update(body.as_bytes());
|
||||
let request_signature = mac.finalize().into_bytes().encode_hex::<String>();
|
||||
|
||||
@@ -300,10 +313,16 @@ pub async fn user_payouts(
|
||||
.1;
|
||||
|
||||
let payout_ids =
|
||||
crate::database::models::payout_item::Payout::get_all_for_user(user.id.into(), &**pool)
|
||||
.await?;
|
||||
let payouts =
|
||||
crate::database::models::payout_item::Payout::get_many(&payout_ids, &**pool).await?;
|
||||
crate::database::models::payout_item::Payout::get_all_for_user(
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let payouts = crate::database::models::payout_item::Payout::get_many(
|
||||
&payout_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(
|
||||
payouts
|
||||
@@ -330,10 +349,17 @@ pub async fn create_payout(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
payouts_queue: web::Data<PayoutsQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let (scopes, user) =
|
||||
get_user_record_from_bearer_token(&req, None, &**pool, &redis, &session_queue)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::Authentication(AuthenticationError::InvalidCredentials))?;
|
||||
let (scopes, user) = get_user_record_from_bearer_token(
|
||||
&req,
|
||||
None,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::Authentication(AuthenticationError::InvalidCredentials)
|
||||
})?;
|
||||
|
||||
if !scopes.contains(Scopes::PAYOUTS_WRITE) {
|
||||
return Err(ApiError::Authentication(
|
||||
@@ -364,7 +390,11 @@ pub async fn create_payout(
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|x| x.id == body.method_id)
|
||||
.ok_or_else(|| ApiError::InvalidInput("Invalid payment method specified!".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"Invalid payment method specified!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let fee = std::cmp::min(
|
||||
std::cmp::max(
|
||||
@@ -385,43 +415,50 @@ pub async fn create_payout(
|
||||
|
||||
let payout_item = match body.method {
|
||||
PayoutMethodType::Venmo | PayoutMethodType::PayPal => {
|
||||
let (wallet, wallet_type, address, display_address) =
|
||||
if body.method == PayoutMethodType::Venmo {
|
||||
if let Some(venmo) = user.venmo_handle {
|
||||
("Venmo", "user_handle", venmo.clone(), venmo)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Venmo address has not been set for account!".to_string(),
|
||||
));
|
||||
}
|
||||
} else if let Some(paypal_id) = user.paypal_id {
|
||||
if let Some(paypal_country) = user.paypal_country {
|
||||
if &*paypal_country == "US" && &*body.method_id != "paypal_us" {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please use the US PayPal transfer option!".to_string(),
|
||||
));
|
||||
} else if &*paypal_country != "US" && &*body.method_id == "paypal_us" {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please use the International PayPal transfer option!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
(
|
||||
"PayPal",
|
||||
"paypal_id",
|
||||
paypal_id.clone(),
|
||||
user.paypal_email.unwrap_or(paypal_id),
|
||||
)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please re-link your PayPal account!".to_string(),
|
||||
));
|
||||
}
|
||||
let (wallet, wallet_type, address, display_address) = if body.method
|
||||
== PayoutMethodType::Venmo
|
||||
{
|
||||
if let Some(venmo) = user.venmo_handle {
|
||||
("Venmo", "user_handle", venmo.clone(), venmo)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You have not linked a PayPal account!".to_string(),
|
||||
"Venmo address has not been set for account!"
|
||||
.to_string(),
|
||||
));
|
||||
};
|
||||
}
|
||||
} else if let Some(paypal_id) = user.paypal_id {
|
||||
if let Some(paypal_country) = user.paypal_country {
|
||||
if &*paypal_country == "US"
|
||||
&& &*body.method_id != "paypal_us"
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please use the US PayPal transfer option!"
|
||||
.to_string(),
|
||||
));
|
||||
} else if &*paypal_country != "US"
|
||||
&& &*body.method_id == "paypal_us"
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please use the International PayPal transfer option!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
(
|
||||
"PayPal",
|
||||
"paypal_id",
|
||||
paypal_id.clone(),
|
||||
user.paypal_email.unwrap_or(paypal_id),
|
||||
)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Please re-link your PayPal account!".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You have not linked a PayPal account!".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PayPalLink {
|
||||
@@ -433,17 +470,18 @@ pub async fn create_payout(
|
||||
pub links: Vec<PayPalLink>,
|
||||
}
|
||||
|
||||
let mut payout_item = crate::database::models::payout_item::Payout {
|
||||
id: payout_id,
|
||||
user_id: user.id,
|
||||
created: Utc::now(),
|
||||
status: PayoutStatus::InTransit,
|
||||
amount: transfer,
|
||||
fee: Some(fee),
|
||||
method: Some(body.method),
|
||||
method_address: Some(display_address),
|
||||
platform_id: None,
|
||||
};
|
||||
let mut payout_item =
|
||||
crate::database::models::payout_item::Payout {
|
||||
id: payout_id,
|
||||
user_id: user.id,
|
||||
created: Utc::now(),
|
||||
status: PayoutStatus::InTransit,
|
||||
amount: transfer,
|
||||
fee: Some(fee),
|
||||
method: Some(body.method),
|
||||
method_address: Some(display_address),
|
||||
platform_id: None,
|
||||
};
|
||||
|
||||
let res: PayoutsResponse = payouts_queue.make_paypal_request(
|
||||
Method::POST,
|
||||
@@ -494,7 +532,8 @@ pub async fn create_payout(
|
||||
.await
|
||||
{
|
||||
if let Some(data) = res.items.first() {
|
||||
payout_item.platform_id = Some(data.payout_item_id.clone());
|
||||
payout_item.platform_id =
|
||||
Some(data.payout_item_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,17 +543,18 @@ pub async fn create_payout(
|
||||
PayoutMethodType::Tremendous => {
|
||||
if let Some(email) = user.email {
|
||||
if user.email_verified {
|
||||
let mut payout_item = crate::database::models::payout_item::Payout {
|
||||
id: payout_id,
|
||||
user_id: user.id,
|
||||
created: Utc::now(),
|
||||
status: PayoutStatus::InTransit,
|
||||
amount: transfer,
|
||||
fee: Some(fee),
|
||||
method: Some(PayoutMethodType::Tremendous),
|
||||
method_address: Some(email.clone()),
|
||||
platform_id: None,
|
||||
};
|
||||
let mut payout_item =
|
||||
crate::database::models::payout_item::Payout {
|
||||
id: payout_id,
|
||||
user_id: user.id,
|
||||
created: Utc::now(),
|
||||
status: PayoutStatus::InTransit,
|
||||
amount: transfer,
|
||||
fee: Some(fee),
|
||||
method: Some(PayoutMethodType::Tremendous),
|
||||
method_address: Some(email.clone()),
|
||||
platform_id: None,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Reward {
|
||||
@@ -566,12 +606,14 @@ pub async fn create_payout(
|
||||
payout_item
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You must verify your account email to proceed!".to_string(),
|
||||
"You must verify your account email to proceed!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You must add an email to your account to proceed!".to_string(),
|
||||
"You must add an email to your account to proceed!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -585,7 +627,8 @@ pub async fn create_payout(
|
||||
payout_item.insert(&mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -610,7 +653,9 @@ pub async fn cancel_payout(
|
||||
.1;
|
||||
|
||||
let id = info.into_inner().0;
|
||||
let payout = crate::database::models::payout_item::Payout::get(id.into(), &**pool).await?;
|
||||
let payout =
|
||||
crate::database::models::payout_item::Payout::get(id.into(), &**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(payout) = payout {
|
||||
if payout.user_id != user.id.into() && !user.role.is_admin() {
|
||||
@@ -630,7 +675,10 @@ pub async fn cancel_payout(
|
||||
payouts
|
||||
.make_paypal_request::<(), ()>(
|
||||
Method::POST,
|
||||
&format!("payments/payouts-item/{}/cancel", platform_id),
|
||||
&format!(
|
||||
"payments/payouts-item/{}/cancel",
|
||||
platform_id
|
||||
),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -792,7 +840,9 @@ async fn get_user_balance(
|
||||
.unwrap_or((Decimal::ZERO, Decimal::ZERO));
|
||||
|
||||
Ok(UserBalance {
|
||||
available: available.round_dp(16) - withdrawn.round_dp(16) - fees.round_dp(16),
|
||||
available: available.round_dp(16)
|
||||
- withdrawn.round_dp(16)
|
||||
- fees.round_dp(16),
|
||||
pending,
|
||||
})
|
||||
}
|
||||
@@ -837,14 +887,19 @@ pub async fn platform_revenue(
|
||||
.and_then(|x| x.sum)
|
||||
.unwrap_or(Decimal::ZERO);
|
||||
|
||||
let points =
|
||||
make_aditude_request(&["METRIC_REVENUE", "METRIC_IMPRESSIONS"], "30d", "1d").await?;
|
||||
let points = make_aditude_request(
|
||||
&["METRIC_REVENUE", "METRIC_IMPRESSIONS"],
|
||||
"30d",
|
||||
"1d",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut points_map = HashMap::new();
|
||||
|
||||
for point in points {
|
||||
for point in point.points_list {
|
||||
let entry = points_map.entry(point.time.seconds).or_insert((None, None));
|
||||
let entry =
|
||||
points_map.entry(point.time.seconds).or_insert((None, None));
|
||||
|
||||
if let Some(revenue) = point.metric.revenue {
|
||||
entry.0 = Some(revenue);
|
||||
@@ -868,7 +923,8 @@ pub async fn platform_revenue(
|
||||
.and_utc()
|
||||
.timestamp();
|
||||
|
||||
if let Some((revenue, impressions)) = points_map.remove(&(start as u64)) {
|
||||
if let Some((revenue, impressions)) = points_map.remove(&(start as u64))
|
||||
{
|
||||
// Before 9/5/24, when legacy payouts were in effect.
|
||||
if start >= 1725494400 {
|
||||
let revenue = revenue.unwrap_or(Decimal::ZERO);
|
||||
@@ -879,8 +935,9 @@ pub async fn platform_revenue(
|
||||
// Clean.io fee (ad antimalware). Per 1000 impressions.
|
||||
let clean_io_fee = Decimal::from(8) / Decimal::from(1000);
|
||||
|
||||
let net_revenue =
|
||||
revenue - (clean_io_fee * Decimal::from(impressions) / Decimal::from(1000));
|
||||
let net_revenue = revenue
|
||||
- (clean_io_fee * Decimal::from(impressions)
|
||||
/ Decimal::from(1000));
|
||||
|
||||
let payout = net_revenue * (Decimal::from(1) - modrinth_cut);
|
||||
|
||||
@@ -903,7 +960,12 @@ pub async fn platform_revenue(
|
||||
};
|
||||
|
||||
redis
|
||||
.set_serialized_to_json(PLATFORM_REVENUE_NAMESPACE, 0, &res, Some(60 * 60))
|
||||
.set_serialized_to_json(
|
||||
PLATFORM_REVENUE_NAMESPACE,
|
||||
0,
|
||||
&res,
|
||||
Some(60 * 60),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(res))
|
||||
@@ -918,7 +980,8 @@ fn get_legacy_data_point(timestamp: u64) -> RevenueData {
|
||||
let weekdays = Decimal::from(20);
|
||||
let weekend_bonus = Decimal::from(5) / Decimal::from(4);
|
||||
|
||||
let weekday_amount = old_payouts_budget / (weekdays + (weekend_bonus) * (days - weekdays));
|
||||
let weekday_amount =
|
||||
old_payouts_budget / (weekdays + (weekend_bonus) * (days - weekdays));
|
||||
let weekend_amount = weekday_amount * weekend_bonus;
|
||||
|
||||
let payout = match start.weekday() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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};
|
||||
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;
|
||||
@@ -11,7 +13,8 @@ use crate::models::ids::{ImageId, OrganizationId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{
|
||||
License, Link, MonetizationStatus, ProjectId, ProjectStatus, VersionId, VersionStatus,
|
||||
License, Link, MonetizationStatus, ProjectId, ProjectStatus, VersionId,
|
||||
VersionStatus,
|
||||
};
|
||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use crate::models::threads::ThreadType;
|
||||
@@ -91,10 +94,14 @@ impl actix_web::ResponseError for CreateError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::SqlxDatabaseError(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
CreateError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::IndexingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::FileHostingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CreateError::FileHostingError(..) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
CreateError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
||||
@@ -105,7 +112,9 @@ impl actix_web::ResponseError for CreateError {
|
||||
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
||||
CreateError::CustomAuthenticationError(..) => StatusCode::UNAUTHORIZED,
|
||||
CreateError::CustomAuthenticationError(..) => {
|
||||
StatusCode::UNAUTHORIZED
|
||||
}
|
||||
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
||||
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||
CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST,
|
||||
@@ -192,7 +201,9 @@ pub struct ProjectCreateData {
|
||||
/// An optional link to the project's license page
|
||||
pub license_url: Option<String>,
|
||||
/// An optional list of all donation links the project has
|
||||
#[validate(custom(function = "crate::util::validate::validate_url_hashmap_values"))]
|
||||
#[validate(custom(
|
||||
function = "crate::util::validate::validate_url_hashmap_values"
|
||||
))]
|
||||
#[serde(default)]
|
||||
pub link_urls: HashMap<String, String>,
|
||||
|
||||
@@ -343,8 +354,10 @@ async fn project_create_inner(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
||||
let all_loaders = models::loader_fields::Loader::list(&mut **transaction, redis).await?;
|
||||
let project_id: ProjectId =
|
||||
models::generate_project_id(transaction).await?.into();
|
||||
let all_loaders =
|
||||
models::loader_fields::Loader::list(&mut **transaction, redis).await?;
|
||||
|
||||
let project_create_data: ProjectCreateData;
|
||||
let mut versions;
|
||||
@@ -365,9 +378,9 @@ async fn project_create_inner(
|
||||
})?;
|
||||
|
||||
let content_disposition = field.content_disposition();
|
||||
let name = content_disposition
|
||||
.get_name()
|
||||
.ok_or_else(|| CreateError::MissingValueError(String::from("Missing content name")))?;
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError(String::from("Missing content name"))
|
||||
})?;
|
||||
|
||||
if name != "data" {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
@@ -377,19 +390,22 @@ async fn project_create_inner(
|
||||
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
data.extend_from_slice(
|
||||
&chunk.map_err(CreateError::MultipartError)?,
|
||||
);
|
||||
}
|
||||
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
||||
|
||||
create_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||
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();
|
||||
|
||||
if let Some(slug_project_id) = slug_project_id_option {
|
||||
let slug_project_id: models::ids::ProjectId = slug_project_id.into();
|
||||
let slug_project_id: models::ids::ProjectId =
|
||||
slug_project_id.into();
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
||||
@@ -602,9 +618,14 @@ async fn project_create_inner(
|
||||
}
|
||||
|
||||
// Convert the list of category names to actual categories
|
||||
let mut categories = Vec::with_capacity(project_create_data.categories.len());
|
||||
let mut categories =
|
||||
Vec::with_capacity(project_create_data.categories.len());
|
||||
for category in &project_create_data.categories {
|
||||
let ids = models::categories::Category::get_ids(category, &mut **transaction).await?;
|
||||
let ids = models::categories::Category::get_ids(
|
||||
category,
|
||||
&mut **transaction,
|
||||
)
|
||||
.await?;
|
||||
if ids.is_empty() {
|
||||
return Err(CreateError::InvalidCategory(category.clone()));
|
||||
}
|
||||
@@ -617,7 +638,11 @@ async fn project_create_inner(
|
||||
let mut additional_categories =
|
||||
Vec::with_capacity(project_create_data.additional_categories.len());
|
||||
for category in &project_create_data.additional_categories {
|
||||
let ids = models::categories::Category::get_ids(category, &mut **transaction).await?;
|
||||
let ids = models::categories::Category::get_ids(
|
||||
category,
|
||||
&mut **transaction,
|
||||
)
|
||||
.await?;
|
||||
if ids.is_empty() {
|
||||
return Err(CreateError::InvalidCategory(category.clone()));
|
||||
}
|
||||
@@ -629,18 +654,29 @@ async fn project_create_inner(
|
||||
let mut members = vec![];
|
||||
|
||||
if let Some(organization_id) = project_create_data.organization_id {
|
||||
let org = models::Organization::get_id(organization_id.into(), pool, redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput("Invalid organization ID specified!".to_string())
|
||||
})?;
|
||||
let org = models::Organization::get_id(
|
||||
organization_id.into(),
|
||||
pool,
|
||||
redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(
|
||||
"Invalid organization ID specified!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let team_member =
|
||||
models::TeamMember::get_from_user_id(org.team_id, current_user.id.into(), pool)
|
||||
.await?;
|
||||
let team_member = models::TeamMember::get_from_user_id(
|
||||
org.team_id,
|
||||
current_user.id.into(),
|
||||
pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let perms =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &team_member);
|
||||
let perms = OrganizationPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&team_member,
|
||||
);
|
||||
|
||||
if !perms
|
||||
.map(|x| x.contains(OrganizationPermissions::ADD_PROJECT))
|
||||
@@ -679,25 +715,32 @@ async fn project_create_inner(
|
||||
}
|
||||
}
|
||||
|
||||
let license_id =
|
||||
spdx::Expression::parse(&project_create_data.license_id).map_err(|err| {
|
||||
CreateError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||
})?;
|
||||
let license_id = spdx::Expression::parse(
|
||||
&project_create_data.license_id,
|
||||
)
|
||||
.map_err(|err| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Invalid SPDX license identifier: {err}"
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut link_urls = vec![];
|
||||
|
||||
let link_platforms =
|
||||
models::categories::LinkPlatform::list(&mut **transaction, redis).await?;
|
||||
models::categories::LinkPlatform::list(&mut **transaction, redis)
|
||||
.await?;
|
||||
for (platform, url) in &project_create_data.link_urls {
|
||||
let platform_id =
|
||||
models::categories::LinkPlatform::get_id(platform, &mut **transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Link platform {} does not exist.",
|
||||
platform.clone()
|
||||
))
|
||||
})?;
|
||||
let platform_id = models::categories::LinkPlatform::get_id(
|
||||
platform,
|
||||
&mut **transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidInput(format!(
|
||||
"Link platform {} does not exist.",
|
||||
platform.clone()
|
||||
))
|
||||
})?;
|
||||
let link_platform = link_platforms
|
||||
.iter()
|
||||
.find(|x| x.id == platform_id)
|
||||
@@ -718,7 +761,9 @@ async fn project_create_inner(
|
||||
let project_builder_actual = models::project_item::ProjectBuilder {
|
||||
project_id: project_id.into(),
|
||||
team_id,
|
||||
organization_id: project_create_data.organization_id.map(|x| x.into()),
|
||||
organization_id: project_create_data
|
||||
.organization_id
|
||||
.map(|x| x.into()),
|
||||
name: project_create_data.name,
|
||||
summary: project_create_data.summary,
|
||||
description: project_create_data.description,
|
||||
@@ -757,8 +802,12 @@ async fn project_create_inner(
|
||||
User::clear_project_cache(&[current_user.id.into()], redis).await?;
|
||||
|
||||
for image_id in project_create_data.uploaded_images {
|
||||
if let Some(db_image) =
|
||||
image_item::Image::get(image_id.into(), &mut **transaction, redis).await?
|
||||
if let Some(db_image) = image_item::Image::get(
|
||||
image_id.into(),
|
||||
&mut **transaction,
|
||||
redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let image: Image = db_image.into();
|
||||
if !matches!(image.context, ImageContext::Project { .. })
|
||||
@@ -884,12 +933,13 @@ async fn create_initial_version(
|
||||
)));
|
||||
}
|
||||
|
||||
version_data
|
||||
.validate()
|
||||
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||
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();
|
||||
let version_id: VersionId =
|
||||
models::generate_version_id(transaction).await?.into();
|
||||
|
||||
let loaders = version_data
|
||||
.loaders
|
||||
@@ -903,10 +953,15 @@ async fn create_initial_version(
|
||||
})
|
||||
.collect::<Result<Vec<models::LoaderId>, CreateError>>()?;
|
||||
|
||||
let loader_fields = LoaderField::get_fields(&loaders, &mut **transaction, redis).await?;
|
||||
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?;
|
||||
LoaderFieldEnumValue::list_many_loader_fields(
|
||||
&loader_fields,
|
||||
&mut **transaction,
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let version_fields = try_create_version_fields(
|
||||
version_id,
|
||||
@@ -954,7 +1009,12 @@ async fn process_icon_upload(
|
||||
file_host: &dyn FileHost,
|
||||
mut field: Field,
|
||||
) -> Result<(String, String, Option<u32>), CreateError> {
|
||||
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let data = read_from_field(
|
||||
&mut field,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
let upload_result = crate::util::img::upload_image_optimized(
|
||||
&format!("data/{}", to_base62(id)),
|
||||
data.freeze(),
|
||||
|
||||
@@ -64,7 +64,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
"members",
|
||||
web::get().to(super::teams::team_members_get_project),
|
||||
)
|
||||
.route("version", 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),
|
||||
@@ -85,9 +88,9 @@ pub async fn random_projects_get(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
count
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
count.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let project_ids = sqlx::query!(
|
||||
"
|
||||
@@ -104,11 +107,12 @@ pub async fn random_projects_get(
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect::<Vec<_>>();
|
||||
let projects_data =
|
||||
db_models::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects_data))
|
||||
}
|
||||
@@ -126,7 +130,8 @@ pub async fn projects_get(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
|
||||
let projects_data = db_models::Project::get_many(&ids, &**pool, &redis).await?;
|
||||
let projects_data =
|
||||
db_models::Project::get_many(&ids, &**pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -139,7 +144,9 @@ pub async fn projects_get(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
let projects = filter_visible_projects(projects_data, &user_option, &pool, false).await?;
|
||||
let projects =
|
||||
filter_visible_projects(projects_data, &user_option, &pool, false)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
@@ -153,7 +160,8 @@ pub async fn project_get(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_data = db_models::Project::get(&string, &**pool, &redis).await?;
|
||||
let project_data =
|
||||
db_models::Project::get(&string, &**pool, &redis).await?;
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
@@ -198,7 +206,9 @@ pub struct EditProject {
|
||||
length(max = 2048)
|
||||
)]
|
||||
pub license_url: Option<Option<String>>,
|
||||
#[validate(custom(function = "crate::util::validate::validate_url_hashmap_optional_values"))]
|
||||
#[validate(custom(
|
||||
function = "crate::util::validate::validate_url_hashmap_optional_values"
|
||||
))]
|
||||
// <name, url> (leave url empty to delete)
|
||||
pub link_urls: Option<HashMap<String, Option<String>>>,
|
||||
pub license_id: Option<String>,
|
||||
@@ -252,9 +262,9 @@ pub async fn project_edit(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_project
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
new_project.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let result = db_models::Project::get(&string, &**pool, &redis).await?;
|
||||
@@ -331,10 +341,12 @@ pub async fn project_edit(
|
||||
if !(user.role.is_mod()
|
||||
|| !project_item.inner.status.is_approved()
|
||||
&& status == &ProjectStatus::Processing
|
||||
|| project_item.inner.status.is_approved() && status.can_be_requested())
|
||||
|| project_item.inner.status.is_approved()
|
||||
&& status.can_be_requested())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to set this status!".to_string(),
|
||||
"You don't have permission to set this status!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -361,7 +373,9 @@ pub async fn project_edit(
|
||||
.insert(project_item.inner.id.into());
|
||||
}
|
||||
|
||||
if status.is_approved() && !project_item.inner.status.is_approved() {
|
||||
if status.is_approved()
|
||||
&& !project_item.inner.status.is_approved()
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -374,7 +388,9 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
if status.is_searchable() && !project_item.inner.webhook_sent {
|
||||
if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") {
|
||||
if let Ok(webhook_url) =
|
||||
dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
||||
{
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
@@ -399,7 +415,9 @@ pub async fn project_edit(
|
||||
}
|
||||
|
||||
if user.role.is_mod() {
|
||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK") {
|
||||
if let Ok(webhook_url) =
|
||||
dotenvy::var("MODERATION_SLACK_WEBHOOK")
|
||||
{
|
||||
crate::util::webhook::send_slack_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
@@ -471,7 +489,9 @@ pub async fn project_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if project_item.inner.status.is_searchable() && !status.is_searchable() {
|
||||
if project_item.inner.status.is_searchable()
|
||||
&& !status.is_searchable()
|
||||
{
|
||||
remove_documents(
|
||||
&project_item
|
||||
.versions
|
||||
@@ -591,7 +611,8 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
let slug_project_id_option: Option<u64> = parse_base62(slug).ok();
|
||||
let slug_project_id_option: Option<u64> =
|
||||
parse_base62(slug).ok();
|
||||
if let Some(slug_project_id) = slug_project_id_option {
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
@@ -604,14 +625,20 @@ pub async fn project_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Slug collides with other project's id!".to_string(),
|
||||
"Slug collides with other project's id!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the new slug is different from the old one
|
||||
// We are able to unwrap here because the slug is always set
|
||||
if !slug.eq(&project_item.inner.slug.clone().unwrap_or_default()) {
|
||||
if !slug.eq(&project_item
|
||||
.inner
|
||||
.slug
|
||||
.clone()
|
||||
.unwrap_or_default())
|
||||
{
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))
|
||||
@@ -623,7 +650,8 @@ pub async fn project_edit(
|
||||
|
||||
if results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Slug collides with other project's id!".to_string(),
|
||||
"Slug collides with other project's id!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -656,7 +684,9 @@ pub async fn project_edit(
|
||||
}
|
||||
|
||||
spdx::Expression::parse(&license).map_err(|err| {
|
||||
ApiError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||
ApiError::InvalidInput(format!(
|
||||
"Invalid SPDX license identifier: {err}"
|
||||
))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -700,17 +730,20 @@ pub async fn project_edit(
|
||||
|
||||
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()
|
||||
))
|
||||
})?;
|
||||
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)
|
||||
@@ -728,7 +761,8 @@ pub async fn project_edit(
|
||||
}
|
||||
if let Some(moderation_message) = &new_project.moderation_message {
|
||||
if !user.role.is_mod()
|
||||
&& (!project_item.inner.status.is_approved() || moderation_message.is_some())
|
||||
&& (!project_item.inner.status.is_approved()
|
||||
|| moderation_message.is_some())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the moderation message of this project!"
|
||||
@@ -749,7 +783,9 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(moderation_message_body) = &new_project.moderation_message_body {
|
||||
if let Some(moderation_message_body) =
|
||||
&new_project.moderation_message_body
|
||||
{
|
||||
if !user.role.is_mod()
|
||||
&& (!project_item.inner.status.is_approved()
|
||||
|| moderation_message_body.is_some())
|
||||
@@ -794,7 +830,8 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(monetization_status) = &new_project.monetization_status {
|
||||
if let Some(monetization_status) = &new_project.monetization_status
|
||||
{
|
||||
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the monetization status of this project!"
|
||||
@@ -802,7 +839,8 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
if (*monetization_status == MonetizationStatus::ForceDemonetized
|
||||
if (*monetization_status
|
||||
== MonetizationStatus::ForceDemonetized
|
||||
|| project_item.inner.monetization_status
|
||||
== MonetizationStatus::ForceDemonetized)
|
||||
&& !user.role.is_mod()
|
||||
@@ -828,16 +866,23 @@ 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.summary]
|
||||
.into_iter()
|
||||
.filter_map(|x| x.as_ref().map(|y| y.as_str()))
|
||||
.collect();
|
||||
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();
|
||||
|
||||
let context = ImageContext::Project {
|
||||
project_id: Some(id.into()),
|
||||
};
|
||||
|
||||
img::delete_unused_images(context, checkable_strings, &mut transaction, &redis).await?;
|
||||
img::delete_unused_images(
|
||||
context,
|
||||
checkable_strings,
|
||||
&mut transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(
|
||||
@@ -875,8 +920,11 @@ pub async fn edit_project_categories(
|
||||
|
||||
let mut mod_categories = Vec::new();
|
||||
for category in categories {
|
||||
let category_ids =
|
||||
db_models::categories::Category::get_ids(category, &mut **transaction).await?;
|
||||
let category_ids = db_models::categories::Category::get_ids(
|
||||
category,
|
||||
&mut **transaction,
|
||||
)
|
||||
.await?;
|
||||
// TODO: We should filter out categories that don't match the project type of any of the versions
|
||||
// ie: if mod and modpack both share a name this should only have modpack if it only has a modpack as a version
|
||||
|
||||
@@ -969,12 +1017,18 @@ pub async fn dependency_list(
|
||||
.ok();
|
||||
|
||||
if let Some(project) = result {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false).await? {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let dependencies =
|
||||
database::Project::get_dependencies(project.inner.id, &**pool, &redis).await?;
|
||||
let dependencies = database::Project::get_dependencies(
|
||||
project.inner.id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let project_ids = dependencies
|
||||
.iter()
|
||||
.filter_map(|x| {
|
||||
@@ -1002,10 +1056,20 @@ pub async fn dependency_list(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut projects =
|
||||
filter_visible_projects(projects_result, &user_option, &pool, false).await?;
|
||||
let mut versions =
|
||||
filter_visible_versions(versions_result, &user_option, &pool, &redis).await?;
|
||||
let mut projects = filter_visible_projects(
|
||||
projects_result,
|
||||
&user_option,
|
||||
&pool,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let mut versions = filter_visible_versions(
|
||||
versions_result,
|
||||
&user_option,
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
projects.sort_by(|a, b| b.published.cmp(&a.published));
|
||||
projects.dedup_by(|a, b| a.id == b.id);
|
||||
@@ -1040,7 +1104,9 @@ pub struct BulkEditProject {
|
||||
pub add_additional_categories: Option<Vec<String>>,
|
||||
pub remove_additional_categories: Option<Vec<String>>,
|
||||
|
||||
#[validate(custom(function = " crate::util::validate::validate_url_hashmap_optional_values"))]
|
||||
#[validate(custom(
|
||||
function = " crate::util::validate::validate_url_hashmap_optional_values"
|
||||
))]
|
||||
pub link_urls: Option<HashMap<String, Option<String>>>,
|
||||
}
|
||||
|
||||
@@ -1062,16 +1128,18 @@ pub async fn projects_edit(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
bulk_edit_project
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
bulk_edit_project.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let project_ids: Vec<db_ids::ProjectId> = serde_json::from_str::<Vec<ProjectId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
let project_ids: Vec<db_ids::ProjectId> =
|
||||
serde_json::from_str::<Vec<ProjectId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis).await?;
|
||||
let projects_data =
|
||||
db_models::Project::get_many_ids(&project_ids, &**pool, &redis).await?;
|
||||
|
||||
if let Some(id) = project_ids
|
||||
.iter()
|
||||
@@ -1087,47 +1155,62 @@ pub async fn projects_edit(
|
||||
.iter()
|
||||
.map(|x| x.inner.team_id)
|
||||
.collect::<Vec<db_models::TeamId>>();
|
||||
let team_members =
|
||||
db_models::TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||
let team_members = db_models::TeamMember::get_from_team_full_many(
|
||||
&team_ids, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_ids = projects_data
|
||||
.iter()
|
||||
.filter_map(|x| x.inner.organization_id)
|
||||
.collect::<Vec<db_models::OrganizationId>>();
|
||||
let organizations =
|
||||
db_models::Organization::get_many_ids(&organization_ids, &**pool, &redis).await?;
|
||||
let organizations = db_models::Organization::get_many_ids(
|
||||
&organization_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_team_ids = organizations
|
||||
.iter()
|
||||
.map(|x| x.team_id)
|
||||
.collect::<Vec<db_models::TeamId>>();
|
||||
let organization_team_members =
|
||||
db_models::TeamMember::get_from_team_full_many(&organization_team_ids, &**pool, &redis)
|
||||
.await?;
|
||||
db_models::TeamMember::get_from_team_full_many(
|
||||
&organization_team_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let categories = db_models::categories::Category::list(&**pool, &redis).await?;
|
||||
let link_platforms = db_models::categories::LinkPlatform::list(&**pool, &redis).await?;
|
||||
let categories =
|
||||
db_models::categories::Category::list(&**pool, &redis).await?;
|
||||
let link_platforms =
|
||||
db_models::categories::LinkPlatform::list(&**pool, &redis).await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
for project in projects_data {
|
||||
if !user.role.is_mod() {
|
||||
let team_member = team_members
|
||||
.iter()
|
||||
.find(|x| x.team_id == project.inner.team_id && x.user_id == user.id.into());
|
||||
let team_member = team_members.iter().find(|x| {
|
||||
x.team_id == project.inner.team_id
|
||||
&& x.user_id == user.id.into()
|
||||
});
|
||||
|
||||
let organization = project
|
||||
.inner
|
||||
.organization_id
|
||||
.and_then(|oid| organizations.iter().find(|x| x.id == oid));
|
||||
|
||||
let organization_team_member = if let Some(organization) = organization {
|
||||
organization_team_members
|
||||
.iter()
|
||||
.find(|x| x.team_id == organization.team_id && x.user_id == user.id.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let organization_team_member =
|
||||
if let Some(organization) = organization {
|
||||
organization_team_members.iter().find(|x| {
|
||||
x.team_id == organization.team_id
|
||||
&& x.user_id == user.id.into()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let permissions = ProjectPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
@@ -1232,7 +1315,13 @@ pub async fn projects_edit(
|
||||
}
|
||||
}
|
||||
|
||||
db_models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis).await?;
|
||||
db_models::Project::clear_cache(
|
||||
project.inner.id,
|
||||
project.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
@@ -1249,15 +1338,17 @@ pub async fn bulk_edit_project_categories(
|
||||
is_additional: bool,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), ApiError> {
|
||||
let mut set_categories = if let Some(categories) = bulk_changes.categories.clone() {
|
||||
categories
|
||||
} else {
|
||||
project_categories.clone()
|
||||
};
|
||||
let mut set_categories =
|
||||
if let Some(categories) = bulk_changes.categories.clone() {
|
||||
categories
|
||||
} else {
|
||||
project_categories.clone()
|
||||
};
|
||||
|
||||
if let Some(delete_categories) = &bulk_changes.remove_categories {
|
||||
for category in delete_categories {
|
||||
if let Some(pos) = set_categories.iter().position(|x| x == category) {
|
||||
if let Some(pos) = set_categories.iter().position(|x| x == category)
|
||||
{
|
||||
set_categories.remove(pos);
|
||||
}
|
||||
}
|
||||
@@ -1291,10 +1382,17 @@ pub async fn bulk_edit_project_categories(
|
||||
.iter()
|
||||
.find(|x| x.category == category)
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!("Category {} does not exist.", category.clone()))
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?
|
||||
.id;
|
||||
mod_categories.push(ModCategory::new(project_id, category_id, is_additional));
|
||||
mod_categories.push(ModCategory::new(
|
||||
project_id,
|
||||
category_id,
|
||||
is_additional,
|
||||
));
|
||||
}
|
||||
ModCategory::insert_many(mod_categories, &mut *transaction).await?;
|
||||
}
|
||||
@@ -1332,7 +1430,9 @@ pub async fn project_icon_edit(
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
@@ -1360,7 +1460,8 @@ pub async fn project_icon_edit(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
"You don't have permission to edit this project's icon."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1372,8 +1473,12 @@ pub async fn project_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let project_id: ProjectId = project_item.inner.id.into();
|
||||
let upload_result = upload_image_optimized(
|
||||
@@ -1403,8 +1508,13 @@ pub async fn project_icon_edit(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -1431,7 +1541,9 @@ pub async fn delete_project_icon(
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
@@ -1458,7 +1570,8 @@ pub async fn delete_project_icon(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
"You don't have permission to edit this project's icon."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1484,8 +1597,13 @@ pub async fn delete_project_icon(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -1512,8 +1630,9 @@ pub async fn add_gallery_item(
|
||||
mut payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
item.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
item.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
@@ -1529,12 +1648,15 @@ pub async fn add_gallery_item(
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if project_item.gallery_items.len() > 64 {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You have reached the maximum of gallery images to upload.".to_string(),
|
||||
"You have reached the maximum of gallery images to upload."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1563,7 +1685,8 @@ pub async fn add_gallery_item(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1621,11 +1744,21 @@ pub async fn add_gallery_item(
|
||||
created: Utc::now(),
|
||||
ordering: item.ordering.unwrap_or(0),
|
||||
}];
|
||||
GalleryItem::insert_many(gallery_item, project_item.inner.id, &mut transaction).await?;
|
||||
GalleryItem::insert_many(
|
||||
gallery_item,
|
||||
project_item.inner.id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -1671,13 +1804,16 @@ pub async fn edit_gallery_item(
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
item.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
item.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
@@ -1704,7 +1840,8 @@ pub async fn edit_gallery_item(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1798,8 +1935,13 @@ pub async fn edit_gallery_item(
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -1832,7 +1974,9 @@ pub async fn delete_gallery_item(
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
@@ -1860,7 +2004,8 @@ pub async fn delete_gallery_item(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
"You don't have permission to edit this project's gallery."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1903,8 +2048,13 @@ pub async fn delete_gallery_item(
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
None,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -1931,7 +2081,9 @@ pub async fn project_delete(
|
||||
let project = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
@@ -1968,7 +2120,8 @@ pub async fn project_delete(
|
||||
let context = ImageContext::Project {
|
||||
project_id: Some(project.inner.id.into()),
|
||||
};
|
||||
let uploaded_images = db_models::Image::get_many_contexted(context, &mut transaction).await?;
|
||||
let uploaded_images =
|
||||
db_models::Image::get_many_contexted(context, &mut transaction).await?;
|
||||
for image in uploaded_images {
|
||||
image_item::Image::remove(image.id, &mut transaction, &redis).await?;
|
||||
}
|
||||
@@ -1983,7 +2136,9 @@ pub async fn project_delete(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let result = db_models::Project::remove(project.inner.id, &mut transaction, &redis).await?;
|
||||
let result =
|
||||
db_models::Project::remove(project.inner.id, &mut transaction, &redis)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -2025,7 +2180,9 @@ pub async fn project_follow(
|
||||
let result = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_id: db_ids::UserId = user.id.into();
|
||||
@@ -2103,7 +2260,9 @@ pub async fn project_unfollow(
|
||||
let result = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let user_id: db_ids::UserId = user.id.into();
|
||||
@@ -2179,7 +2338,9 @@ pub async fn project_get_organization(
|
||||
let result = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !is_visible_project(&result.inner, ¤t_user, &pool, false).await? {
|
||||
@@ -2187,14 +2348,21 @@ pub async fn project_get_organization(
|
||||
"The specified project does not exist!".to_string(),
|
||||
))
|
||||
} else if let Some(organization_id) = result.inner.organization_id {
|
||||
let organization = db_models::Organization::get_id(organization_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The attached organization does not exist!".to_string())
|
||||
})?;
|
||||
let organization =
|
||||
db_models::Organization::get_id(organization_id, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The attached organization does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(organization.team_id, &**pool, &redis).await?;
|
||||
let members_data = TeamMember::get_from_team_full(
|
||||
organization.team_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
@@ -2216,17 +2384,26 @@ pub async fn project_get_organization(
|
||||
logged_in
|
||||
|| x.accepted
|
||||
|| user_id
|
||||
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||
.map(|y: crate::database::models::UserId| {
|
||||
y == x.user_id
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let organization = models::organizations::Organization::from(organization, team_members);
|
||||
let organization = models::organizations::Organization::from(
|
||||
organization,
|
||||
team_members,
|
||||
);
|
||||
return Ok(HttpResponse::Ok().json(organization));
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||
use crate::database;
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
||||
use crate::database::models::thread_item::{
|
||||
ThreadBuilder, ThreadMessageBuilder,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||
use crate::models::ids::{
|
||||
base62_impl::parse_base62, ProjectId, UserId, VersionId,
|
||||
};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::reports::{ItemType, Report};
|
||||
@@ -62,19 +66,25 @@ pub async fn report_create(
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = body.next().await {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInput("Error while parsing request payload!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"Error while parsing request payload!".to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
let new_report: CreateReport = serde_json::from_slice(bytes.as_ref())?;
|
||||
|
||||
let id = crate::database::models::generate_report_id(&mut transaction).await?;
|
||||
let id =
|
||||
crate::database::models::generate_report_id(&mut transaction).await?;
|
||||
let report_type = crate::database::models::categories::ReportType::get_id(
|
||||
&new_report.report_type,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type))
|
||||
ApiError::InvalidInput(format!(
|
||||
"Invalid report type: {}",
|
||||
new_report.report_type
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut report = crate::database::models::report_item::Report {
|
||||
@@ -91,7 +101,8 @@ pub async fn report_create(
|
||||
|
||||
match new_report.item_type {
|
||||
ItemType::Project => {
|
||||
let project_id = ProjectId(parse_base62(new_report.item_id.as_str())?);
|
||||
let project_id =
|
||||
ProjectId(parse_base62(new_report.item_id.as_str())?);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM mods WHERE id = $1)",
|
||||
@@ -110,7 +121,8 @@ pub async fn report_create(
|
||||
report.project_id = Some(project_id.into())
|
||||
}
|
||||
ItemType::Version => {
|
||||
let version_id = VersionId(parse_base62(new_report.item_id.as_str())?);
|
||||
let version_id =
|
||||
VersionId(parse_base62(new_report.item_id.as_str())?);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)",
|
||||
@@ -159,7 +171,8 @@ pub async fn report_create(
|
||||
|
||||
for image_id in new_report.uploaded_images {
|
||||
if let Some(db_image) =
|
||||
image_item::Image::get(image_id.into(), &mut *transaction, &redis).await?
|
||||
image_item::Image::get(image_id.into(), &mut *transaction, &redis)
|
||||
.await?
|
||||
{
|
||||
let image: Image = db_image.into();
|
||||
if !matches!(image.context, ImageContext::Report { .. })
|
||||
@@ -281,8 +294,11 @@ pub async fn reports(
|
||||
.await?
|
||||
};
|
||||
|
||||
let query_reports =
|
||||
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||
let query_reports = crate::database::models::report_item::Report::get_many(
|
||||
&report_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut reports: Vec<Report> = Vec::new();
|
||||
|
||||
@@ -311,8 +327,11 @@ pub async fn reports_get(
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let reports_data =
|
||||
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||
let reports_data = crate::database::models::report_item::Report::get_many(
|
||||
&report_ids,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
@@ -351,7 +370,8 @@ pub async fn report_get(
|
||||
.1;
|
||||
let id = info.into_inner().0.into();
|
||||
|
||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
let report =
|
||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if !user.role.is_mod() && report.reporter != user.id.into() {
|
||||
@@ -391,7 +411,8 @@ pub async fn report_edit(
|
||||
.1;
|
||||
let id = info.into_inner().0.into();
|
||||
|
||||
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
let report =
|
||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if !user.role.is_mod() && report.reporter != user.id.into() {
|
||||
@@ -455,8 +476,13 @@ pub async fn report_edit(
|
||||
let image_context = ImageContext::Report {
|
||||
report_id: Some(id.into()),
|
||||
};
|
||||
img::delete_unused_images(image_context, checkable_strings, &mut transaction, &redis)
|
||||
.await?;
|
||||
img::delete_unused_images(
|
||||
image_context,
|
||||
checkable_strings,
|
||||
&mut transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
@@ -489,14 +515,17 @@ pub async fn report_delete(
|
||||
report_id: Some(id),
|
||||
};
|
||||
let uploaded_images =
|
||||
database::models::Image::get_many_contexted(context, &mut transaction).await?;
|
||||
database::models::Image::get_many_contexted(context, &mut transaction)
|
||||
.await?;
|
||||
for image in uploaded_images {
|
||||
image_item::Image::remove(image.id, &mut transaction, &redis).await?;
|
||||
}
|
||||
|
||||
let result =
|
||||
crate::database::models::report_item::Report::remove_full(id.into(), &mut transaction)
|
||||
.await?;
|
||||
let result = crate::database::models::report_item::Report::remove_full(
|
||||
id.into(),
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
|
||||
if result.is_some() {
|
||||
|
||||
@@ -14,7 +14,9 @@ pub struct V3Stats {
|
||||
pub files: Option<i64>,
|
||||
}
|
||||
|
||||
pub async fn get_stats(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn get_stats(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let projects = sqlx::query!(
|
||||
"
|
||||
SELECT COUNT(id)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::ApiError;
|
||||
use crate::database::models::categories::{Category, LinkPlatform, ProjectType, ReportType};
|
||||
use crate::database::models::categories::{
|
||||
Category, LinkPlatform, ProjectType, ReportType,
|
||||
};
|
||||
use crate::database::models::loader_fields::{
|
||||
Game, Loader, LoaderField, LoaderFieldEnumValue, LoaderFieldType,
|
||||
};
|
||||
@@ -147,7 +149,8 @@ pub async fn loader_fields_list(
|
||||
})?;
|
||||
|
||||
let loader_field_enum_id = match loader_field.field_type {
|
||||
LoaderFieldType::Enum(enum_id) | LoaderFieldType::ArrayEnum(enum_id) => enum_id,
|
||||
LoaderFieldType::Enum(enum_id)
|
||||
| LoaderFieldType::ArrayEnum(enum_id) => enum_id,
|
||||
_ => {
|
||||
return Err(ApiError::InvalidInput(format!(
|
||||
"'{}' is not an enumerable field, but an '{}' field.",
|
||||
@@ -158,9 +161,16 @@ pub async fn loader_fields_list(
|
||||
};
|
||||
|
||||
let results: Vec<_> = if let Some(filters) = query.filters {
|
||||
LoaderFieldEnumValue::list_filter(loader_field_enum_id, filters, &**pool, &redis).await?
|
||||
LoaderFieldEnumValue::list_filter(
|
||||
loader_field_enum_id,
|
||||
filters,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
LoaderFieldEnumValue::list(loader_field_enum_id, &**pool, &redis).await?
|
||||
LoaderFieldEnumValue::list(loader_field_enum_id, &**pool, &redis)
|
||||
.await?
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
@@ -192,7 +202,9 @@ pub struct LicenseText {
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse, ApiError> {
|
||||
pub async fn license_text(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let license_id = params.into_inner().0;
|
||||
|
||||
if license_id == *crate::models::projects::DEFAULT_LICENSE_ID {
|
||||
@@ -224,14 +236,15 @@ pub async fn link_platform_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let results: Vec<LinkPlatformQueryData> = LinkPlatform::list(&**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| LinkPlatformQueryData {
|
||||
name: x.name,
|
||||
donation: x.donation,
|
||||
})
|
||||
.collect();
|
||||
let results: Vec<LinkPlatformQueryData> =
|
||||
LinkPlatform::list(&**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| LinkPlatformQueryData {
|
||||
name: x.name,
|
||||
donation: x.donation,
|
||||
})
|
||||
.collect();
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::database::redis::RedisPool;
|
||||
use crate::database::Project;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId};
|
||||
use crate::models::teams::{
|
||||
OrganizationPermissions, ProjectPermissions, TeamId,
|
||||
};
|
||||
use crate::models::users::UserId;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
@@ -46,7 +48,8 @@ pub async fn team_members_get_project(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
||||
let project_data =
|
||||
crate::database::models::Project::get(&string, &**pool, &redis).await?;
|
||||
|
||||
if let Some(project) = project_data {
|
||||
let current_user = get_user_from_headers(
|
||||
@@ -60,11 +63,17 @@ pub async fn team_members_get_project(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
if !is_visible_project(&project.inner, ¤t_user, &pool, false).await? {
|
||||
if !is_visible_project(&project.inner, ¤t_user, &pool, false)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
|
||||
let members_data = TeamMember::get_from_team_full(
|
||||
project.inner.team_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let users = User::get_many_ids(
|
||||
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -75,7 +84,12 @@ pub async fn team_members_get_project(
|
||||
let user_id = current_user.as_ref().map(|x| x.id.into());
|
||||
let logged_in = if let Some(user_id) = user_id {
|
||||
let (team_member, organization_team_member) =
|
||||
TeamMember::get_for_project_permissions(&project.inner, user_id, &**pool).await?;
|
||||
TeamMember::get_for_project_permissions(
|
||||
&project.inner,
|
||||
user_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
team_member.is_some() || organization_team_member.is_some()
|
||||
} else {
|
||||
@@ -88,12 +102,18 @@ pub async fn team_members_get_project(
|
||||
logged_in
|
||||
|| x.accepted
|
||||
|| user_id
|
||||
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||
.map(|y: crate::database::models::UserId| {
|
||||
y == x.user_id
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -113,7 +133,8 @@ pub async fn team_members_get_organization(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
let organization_data =
|
||||
crate::database::models::Organization::get(&string, &**pool, &redis).await?;
|
||||
crate::database::models::Organization::get(&string, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
if let Some(organization) = organization_data {
|
||||
let current_user = get_user_from_headers(
|
||||
@@ -127,8 +148,12 @@ pub async fn team_members_get_organization(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(organization.team_id, &**pool, &redis).await?;
|
||||
let members_data = TeamMember::get_from_team_full(
|
||||
organization.team_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -152,12 +177,18 @@ pub async fn team_members_get_organization(
|
||||
logged_in
|
||||
|| x.accepted
|
||||
|| user_id
|
||||
.map(|y: crate::database::models::UserId| y == x.user_id)
|
||||
.map(|y: crate::database::models::UserId| {
|
||||
y == x.user_id
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -177,7 +208,8 @@ pub async fn team_members_get(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let id = info.into_inner().0;
|
||||
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
||||
let members_data =
|
||||
TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -215,10 +247,13 @@ pub async fn team_members_get(
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.flat_map(|data| {
|
||||
users
|
||||
.iter()
|
||||
.find(|x| x.id == data.user_id)
|
||||
.map(|user| crate::models::teams::TeamMember::from(data, user.clone(), !logged_in))
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -244,7 +279,8 @@ pub async fn teams_get(
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
||||
|
||||
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||
let teams_data =
|
||||
TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
|
||||
let users = crate::database::models::User::get_many_ids(
|
||||
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -284,7 +320,11 @@ pub async fn teams_get(
|
||||
.filter(|x| logged_in || x.accepted)
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -312,8 +352,12 @@ pub async fn join_team(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let member =
|
||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||
let member = TeamMember::get_from_user_id_pending(
|
||||
team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(member) = member {
|
||||
if member.accepted {
|
||||
@@ -398,19 +442,33 @@ pub async fn add_team_member(
|
||||
.1;
|
||||
let team_association = Team::get_association(team_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("The team specified does not exist".to_string()))?;
|
||||
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool).await?;
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The team specified does not exist".to_string(),
|
||||
)
|
||||
})?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||
.await?;
|
||||
match team_association {
|
||||
// If team is associated with a project, check if they have permissions to invite users to that project
|
||||
TeamAssociationId::Project(pid) => {
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(pid, &**pool).await?;
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(organization.team_id, current_user.id.into(), &**pool)
|
||||
Organization::get_associated_organization_project_id(
|
||||
pid, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let organization_team_member =
|
||||
if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let permissions = ProjectPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&member,
|
||||
@@ -420,12 +478,14 @@ pub async fn add_team_member(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::MANAGE_INVITES) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to invite users to this team".to_string(),
|
||||
"You don't have permission to invite users to this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
if !permissions.contains(new_member.permissions) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The new member has permissions that you don't have".to_string(),
|
||||
"The new member has permissions that you don't have"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -439,23 +499,28 @@ pub async fn add_team_member(
|
||||
// If team is associated with an organization, check if they have permissions to invite users to that organization
|
||||
TeamAssociationId::Organization(_) => {
|
||||
let organization_permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &member)
|
||||
.unwrap_or_default();
|
||||
if !organization_permissions.contains(OrganizationPermissions::MANAGE_INVITES) {
|
||||
OrganizationPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&member,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
if !organization_permissions
|
||||
.contains(OrganizationPermissions::MANAGE_INVITES)
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to invite users to this organization".to_string(),
|
||||
));
|
||||
}
|
||||
if !organization_permissions
|
||||
.contains(new_member.organization_permissions.unwrap_or_default())
|
||||
{
|
||||
if !organization_permissions.contains(
|
||||
new_member.organization_permissions.unwrap_or_default(),
|
||||
) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The new member has organization permissions that you don't have".to_string(),
|
||||
));
|
||||
}
|
||||
if !organization_permissions
|
||||
.contains(OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS)
|
||||
&& !new_member.permissions.is_empty()
|
||||
if !organization_permissions.contains(
|
||||
OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS,
|
||||
) && !new_member.permissions.is_empty()
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to give this user default project permissions. Ensure 'permissions' is set if it is not, and empty (0)."
|
||||
@@ -465,14 +530,20 @@ pub async fn add_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
if new_member.payouts_split < Decimal::ZERO || new_member.payouts_split > Decimal::from(5000) {
|
||||
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(),
|
||||
));
|
||||
}
|
||||
|
||||
let request =
|
||||
TeamMember::get_from_user_id_pending(team_id, new_member.user_id.into(), &**pool).await?;
|
||||
let request = TeamMember::get_from_user_id_pending(
|
||||
team_id,
|
||||
new_member.user_id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(req) = request {
|
||||
if req.accepted {
|
||||
@@ -481,25 +552,38 @@ pub async fn add_team_member(
|
||||
));
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"There is already a pending member request for this user".to_string(),
|
||||
"There is already a pending member request for this user"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let new_user =
|
||||
crate::database::models::User::get_id(new_member.user_id.into(), &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("An invalid User ID specified".to_string()))?;
|
||||
let new_user = crate::database::models::User::get_id(
|
||||
new_member.user_id.into(),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("An invalid User ID specified".to_string())
|
||||
})?;
|
||||
|
||||
let mut force_accepted = false;
|
||||
if let TeamAssociationId::Project(pid) = team_association {
|
||||
// We cannot add the owner to a project team in their own org
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(pid, &**pool).await?;
|
||||
let new_user_organization_team_member = if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(organization.team_id, new_user.id, &**pool).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Organization::get_associated_organization_project_id(pid, &**pool)
|
||||
.await?;
|
||||
let new_user_organization_team_member =
|
||||
if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
new_user.id,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if new_user_organization_team_member
|
||||
.as_ref()
|
||||
.map(|tm| tm.is_owner)
|
||||
@@ -521,7 +605,9 @@ pub async fn add_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
let new_id = crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||
let new_id =
|
||||
crate::database::models::ids::generate_team_member_id(&mut transaction)
|
||||
.await?;
|
||||
TeamMember {
|
||||
id: new_id,
|
||||
team_id,
|
||||
@@ -605,22 +691,30 @@ pub async fn edit_team_member(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let team_association = Team::get_association(id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("The team specified does not exist".to_string()))?;
|
||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool).await?;
|
||||
let edit_member_db = TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
let team_association =
|
||||
Team::get_association(id, &**pool).await?.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The team specified does not exist".to_string(),
|
||||
)
|
||||
})?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?;
|
||||
let edit_member_db =
|
||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if edit_member_db.is_owner
|
||||
&& (edit_member.permissions.is_some() || edit_member.organization_permissions.is_some())
|
||||
&& (edit_member.permissions.is_some()
|
||||
|| edit_member.organization_permissions.is_some())
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The owner's permission's in a team cannot be edited".to_string(),
|
||||
@@ -630,13 +724,21 @@ pub async fn edit_team_member(
|
||||
match team_association {
|
||||
TeamAssociationId::Project(project_id) => {
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(project_id, &**pool).await?;
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(organization.team_id, current_user.id.into(), &**pool)
|
||||
Organization::get_associated_organization_project_id(
|
||||
project_id, &**pool,
|
||||
)
|
||||
.await?;
|
||||
let organization_team_member =
|
||||
if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if organization_team_member
|
||||
.as_ref()
|
||||
@@ -661,7 +763,8 @@ pub async fn edit_team_member(
|
||||
.unwrap_or_default();
|
||||
if !permissions.contains(ProjectPermissions::EDIT_MEMBER) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -682,16 +785,23 @@ pub async fn edit_team_member(
|
||||
}
|
||||
TeamAssociationId::Organization(_) => {
|
||||
let organization_permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &member)
|
||||
.unwrap_or_default();
|
||||
OrganizationPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&member,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !organization_permissions.contains(OrganizationPermissions::EDIT_MEMBER) {
|
||||
if !organization_permissions
|
||||
.contains(OrganizationPermissions::EDIT_MEMBER)
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(new_permissions) = edit_member.organization_permissions {
|
||||
if let Some(new_permissions) = edit_member.organization_permissions
|
||||
{
|
||||
if !organization_permissions.contains(new_permissions) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The new organization permissions have permissions that you don't have"
|
||||
@@ -701,8 +811,9 @@ pub async fn edit_team_member(
|
||||
}
|
||||
|
||||
if edit_member.permissions.is_some()
|
||||
&& !organization_permissions
|
||||
.contains(OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS)
|
||||
&& !organization_permissions.contains(
|
||||
OrganizationPermissions::EDIT_MEMBER_DEFAULT_PERMISSIONS,
|
||||
)
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to give this user default project permissions."
|
||||
@@ -713,7 +824,8 @@ pub async fn edit_team_member(
|
||||
}
|
||||
|
||||
if let Some(payouts_split) = edit_member.payouts_split {
|
||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000) {
|
||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000)
|
||||
{
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Payouts split must be between 0 and 5000!".to_string(),
|
||||
));
|
||||
@@ -782,26 +894,38 @@ pub async fn transfer_ownership(
|
||||
}
|
||||
|
||||
if !current_user.role.is_admin() {
|
||||
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team".to_string(),
|
||||
)
|
||||
})?;
|
||||
let member = TeamMember::get_from_user_id(
|
||||
id.into(),
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit members of this team"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !member.is_owner {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit the ownership of this team".to_string(),
|
||||
"You don't have permission to edit the ownership of this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let new_member = TeamMember::get_from_user_id(id.into(), new_owner.user_id.into(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The new owner specified does not exist".to_string())
|
||||
})?;
|
||||
let new_member = TeamMember::get_from_user_id(
|
||||
id.into(),
|
||||
new_owner.user_id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The new owner specified does not exist".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !new_member.accepted {
|
||||
return Err(ApiError::InvalidInput(
|
||||
@@ -848,7 +972,8 @@ pub async fn transfer_ownership(
|
||||
.await?;
|
||||
|
||||
let project_teams_edited =
|
||||
if let Some(TeamAssociationId::Organization(oid)) = team_association_id {
|
||||
if let Some(TeamAssociationId::Organization(oid)) = team_association_id
|
||||
{
|
||||
// The owner of ALL projects that this organization owns, if applicable, should be removed as members of the project,
|
||||
// if they are members of those projects.
|
||||
// (As they are the org owners for them, and they should not have more specific permissions)
|
||||
@@ -872,7 +997,12 @@ pub async fn transfer_ownership(
|
||||
|
||||
// If the owner of the organization is a member of the project, remove them
|
||||
for team_id in team_ids.iter() {
|
||||
TeamMember::delete(*team_id, new_owner.user_id.into(), &mut transaction).await?;
|
||||
TeamMember::delete(
|
||||
*team_id,
|
||||
new_owner.user_id.into(),
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
team_ids
|
||||
@@ -910,12 +1040,18 @@ pub async fn remove_team_member(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let team_association = Team::get_association(id, &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::InvalidInput("The team specified does not exist".to_string()))?;
|
||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool).await?;
|
||||
let team_association =
|
||||
Team::get_association(id, &**pool).await?.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The team specified does not exist".to_string(),
|
||||
)
|
||||
})?;
|
||||
let member =
|
||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||
.await?;
|
||||
|
||||
let delete_member = TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
||||
let delete_member =
|
||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
||||
|
||||
if let Some(delete_member) = delete_member {
|
||||
if delete_member.is_owner {
|
||||
@@ -931,17 +1067,21 @@ pub async fn remove_team_member(
|
||||
match team_association {
|
||||
TeamAssociationId::Project(pid) => {
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(pid, &**pool).await?;
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
Organization::get_associated_organization_project_id(
|
||||
pid, &**pool,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.await?;
|
||||
let organization_team_member =
|
||||
if let Some(organization) = &organization {
|
||||
TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
current_user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let permissions = ProjectPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&member,
|
||||
@@ -952,18 +1092,22 @@ pub async fn remove_team_member(
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if Some(delete_member.user_id) == member.as_ref().map(|m| m.user_id)
|
||||
|| permissions.contains(ProjectPermissions::REMOVE_MEMBER)
|
||||
if Some(delete_member.user_id)
|
||||
== member.as_ref().map(|m| m.user_id)
|
||||
|| permissions
|
||||
.contains(ProjectPermissions::REMOVE_MEMBER)
|
||||
// true as if the permission exists, but the member does not, they are part of an org
|
||||
{
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
TeamMember::delete(id, user_id, &mut transaction)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to remove a member from this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else if Some(delete_member.user_id) == member.as_ref().map(|m| m.user_id)
|
||||
} else if Some(delete_member.user_id)
|
||||
== member.as_ref().map(|m| m.user_id)
|
||||
|| permissions.contains(ProjectPermissions::MANAGE_INVITES)
|
||||
// true as if the permission exists, but the member does not, they are part of an org
|
||||
{
|
||||
@@ -973,30 +1117,38 @@ pub async fn remove_team_member(
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to cancel a team invite".to_string(),
|
||||
"You do not have permission to cancel a team invite"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
TeamAssociationId::Organization(_) => {
|
||||
let organization_permissions =
|
||||
OrganizationPermissions::get_permissions_by_role(¤t_user.role, &member)
|
||||
.unwrap_or_default();
|
||||
OrganizationPermissions::get_permissions_by_role(
|
||||
¤t_user.role,
|
||||
&member,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
// Organization teams requires a TeamMember, so we can 'unwrap'
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
if Some(delete_member.user_id) == member.map(|m| m.user_id)
|
||||
|| organization_permissions.contains(OrganizationPermissions::REMOVE_MEMBER)
|
||||
|| organization_permissions
|
||||
.contains(OrganizationPermissions::REMOVE_MEMBER)
|
||||
{
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
TeamMember::delete(id, user_id, &mut transaction)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to remove a member from this organization"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else if Some(delete_member.user_id) == member.map(|m| m.user_id)
|
||||
|| organization_permissions.contains(OrganizationPermissions::MANAGE_INVITES)
|
||||
} else if Some(delete_member.user_id)
|
||||
== member.map(|m| m.user_id)
|
||||
|| organization_permissions
|
||||
.contains(OrganizationPermissions::MANAGE_INVITES)
|
||||
{
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
|
||||
@@ -27,7 +27,9 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.route("{id}", web::get().to(thread_get))
|
||||
.route("{id}", web::post().to(thread_send_message)),
|
||||
);
|
||||
cfg.service(web::scope("message").route("{id}", web::delete().to(message_delete)));
|
||||
cfg.service(
|
||||
web::scope("message").route("{id}", web::delete().to(message_delete)),
|
||||
);
|
||||
cfg.route("threads", web::get().to(threads_get));
|
||||
}
|
||||
|
||||
@@ -104,7 +106,8 @@ pub async fn filter_authorized_threads(
|
||||
|
||||
for thread in threads {
|
||||
if user.role.is_mod()
|
||||
|| (thread.type_ == ThreadType::DirectMessage && thread.members.contains(&user_id))
|
||||
|| (thread.type_ == ThreadType::DirectMessage
|
||||
&& thread.members.contains(&user_id))
|
||||
{
|
||||
return_threads.push(thread);
|
||||
} else {
|
||||
@@ -226,11 +229,12 @@ pub async fn filter_authorized_threads(
|
||||
.collect::<Vec<database::models::UserId>>(),
|
||||
);
|
||||
|
||||
let users: Vec<User> = database::models::User::get_many_ids(&user_ids, &***pool, redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
let users: Vec<User> =
|
||||
database::models::User::get_many_ids(&user_ids, &***pool, redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
|
||||
let mut final_threads = Vec::new();
|
||||
|
||||
@@ -304,13 +308,16 @@ pub async fn thread_get(
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let users: Vec<User> = database::models::User::get_many_ids(authors, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
let users: Vec<User> =
|
||||
database::models::User::get_many_ids(authors, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(From::from)
|
||||
.collect();
|
||||
|
||||
return Ok(HttpResponse::Ok().json(Thread::from(data, users, &user)));
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(Thread::from(data, users, &user))
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(ApiError::NotFound)
|
||||
@@ -344,9 +351,11 @@ pub async fn threads_get(
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
|
||||
let threads_data = database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
||||
let threads_data =
|
||||
database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
||||
|
||||
let threads = filter_authorized_threads(threads_data, &user, &pool, &redis).await?;
|
||||
let threads =
|
||||
filter_authorized_threads(threads_data, &user, &pool, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(threads))
|
||||
}
|
||||
@@ -396,13 +405,17 @@ pub async fn thread_send_message(
|
||||
}
|
||||
|
||||
if let Some(replying_to) = replying_to {
|
||||
let thread_message =
|
||||
database::models::ThreadMessage::get((*replying_to).into(), &**pool).await?;
|
||||
let thread_message = database::models::ThreadMessage::get(
|
||||
(*replying_to).into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(thread_message) = thread_message {
|
||||
if thread_message.thread_id != string {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Message replied to is from another thread!".to_string(),
|
||||
"Message replied to is from another thread!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
@@ -436,16 +449,21 @@ pub async fn thread_send_message(
|
||||
.await?;
|
||||
|
||||
if let Some(project_id) = thread.project_id {
|
||||
let project = database::models::Project::get_id(project_id, &**pool, &redis).await?;
|
||||
let project =
|
||||
database::models::Project::get_id(project_id, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
if let Some(project) = project {
|
||||
if project.inner.status != ProjectStatus::Processing && user.role.is_mod() {
|
||||
let members = database::models::TeamMember::get_from_team_full(
|
||||
project.inner.team_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
if project.inner.status != ProjectStatus::Processing
|
||||
&& user.role.is_mod()
|
||||
{
|
||||
let members =
|
||||
database::models::TeamMember::get_from_team_full(
|
||||
project.inner.team_id,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
NotificationBuilder {
|
||||
body: NotificationBody::ModeratorMessage {
|
||||
@@ -464,7 +482,9 @@ pub async fn thread_send_message(
|
||||
}
|
||||
}
|
||||
} else if let Some(report_id) = thread.report_id {
|
||||
let report = database::models::report_item::Report::get(report_id, &**pool).await?;
|
||||
let report =
|
||||
database::models::report_item::Report::get(report_id, &**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(report) = report {
|
||||
if report.closed && !user.role.is_mod() {
|
||||
@@ -493,12 +513,18 @@ pub async fn thread_send_message(
|
||||
} = &new_message.body
|
||||
{
|
||||
for image_id in associated_images {
|
||||
if let Some(db_image) =
|
||||
image_item::Image::get((*image_id).into(), &mut *transaction, &redis).await?
|
||||
if let Some(db_image) = image_item::Image::get(
|
||||
(*image_id).into(),
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let image: Image = db_image.into();
|
||||
if !matches!(image.context, ImageContext::ThreadMessage { .. })
|
||||
|| image.context.inner_id().is_some()
|
||||
if !matches!(
|
||||
image.context,
|
||||
ImageContext::ThreadMessage { .. }
|
||||
) || image.context.inner_id().is_some()
|
||||
{
|
||||
return Err(ApiError::InvalidInput(format!(
|
||||
"Image {} is not unused and in the 'thread_message' context",
|
||||
@@ -518,7 +544,8 @@ pub async fn thread_send_message(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
image_item::Image::clear_cache(image.id.into(), &redis).await?;
|
||||
image_item::Image::clear_cache(image.id.into(), &redis)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(format!(
|
||||
"Image {} does not exist",
|
||||
@@ -554,7 +581,11 @@ pub async fn message_delete(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
||||
let result = database::models::ThreadMessage::get(
|
||||
info.into_inner().0.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(thread) = result {
|
||||
if !user.role.is_mod() && thread.author_id != Some(user.id.into()) {
|
||||
@@ -568,7 +599,9 @@ pub async fn message_delete(
|
||||
let context = ImageContext::ThreadMessage {
|
||||
thread_message_id: Some(thread.id.into()),
|
||||
};
|
||||
let images = database::Image::get_many_contexted(context, &mut transaction).await?;
|
||||
let images =
|
||||
database::Image::get_many_contexted(context, &mut transaction)
|
||||
.await?;
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
for image in images {
|
||||
let name = image.url.split(&format!("{cdn_url}/")).nth(1);
|
||||
@@ -586,7 +619,12 @@ pub async fn message_delete(
|
||||
false
|
||||
};
|
||||
|
||||
database::models::ThreadMessage::remove_full(thread.id, private, &mut transaction).await?;
|
||||
database::models::ThreadMessage::remove_full(
|
||||
thread.id,
|
||||
private,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
@@ -67,9 +67,14 @@ pub async fn projects_list(
|
||||
if let Some(id) = id_option.map(|x| x.id) {
|
||||
let project_data = User::get_projects(id, &**pool, &redis).await?;
|
||||
|
||||
let projects: Vec<_> =
|
||||
crate::database::Project::get_many_ids(&project_data, &**pool, &redis).await?;
|
||||
let projects = filter_visible_projects(projects, &user, &pool, true).await?;
|
||||
let projects: Vec<_> = crate::database::Project::get_many_ids(
|
||||
&project_data,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let projects =
|
||||
filter_visible_projects(projects, &user, &pool, true).await?;
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
@@ -116,7 +121,8 @@ pub async fn users_get(
|
||||
|
||||
let users_data = User::get_many(&user_ids, &**pool, &redis).await?;
|
||||
|
||||
let users: Vec<crate::models::users::User> = users_data.into_iter().map(From::from).collect();
|
||||
let users: Vec<crate::models::users::User> =
|
||||
users_data.into_iter().map(From::from).collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
@@ -165,13 +171,18 @@ pub async fn collections_list(
|
||||
|
||||
let project_data = User::get_collections(id, &**pool).await?;
|
||||
|
||||
let response: Vec<_> =
|
||||
crate::database::models::Collection::get_many(&project_data, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| can_view_private || matches!(x.status, CollectionStatus::Listed))
|
||||
.map(Collection::from)
|
||||
.collect();
|
||||
let response: Vec<_> = crate::database::models::Collection::get_many(
|
||||
&project_data,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| {
|
||||
can_view_private || matches!(x.status, CollectionStatus::Listed)
|
||||
})
|
||||
.map(Collection::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
@@ -213,10 +224,11 @@ pub async fn orgs_list(
|
||||
.map(|x| x.team_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let teams_data = crate::database::models::TeamMember::get_from_team_full_many(
|
||||
&team_ids, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
let teams_data =
|
||||
crate::database::models::TeamMember::get_from_team_full_many(
|
||||
&team_ids, &**pool, &redis,
|
||||
)
|
||||
.await?;
|
||||
let users = User::get_many_ids(
|
||||
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
|
||||
&**pool,
|
||||
@@ -231,7 +243,8 @@ pub async fn orgs_list(
|
||||
}
|
||||
|
||||
for data in organizations_data {
|
||||
let members_data = team_groups.remove(&data.team_id).unwrap_or(vec![]);
|
||||
let members_data =
|
||||
team_groups.remove(&data.team_id).unwrap_or(vec![]);
|
||||
let logged_in = user
|
||||
.as_ref()
|
||||
.and_then(|user| {
|
||||
@@ -246,12 +259,19 @@ pub async fn orgs_list(
|
||||
.filter(|x| logged_in || x.accepted || id == x.user_id)
|
||||
.flat_map(|data| {
|
||||
users.iter().find(|x| x.id == data.user_id).map(|user| {
|
||||
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
|
||||
crate::models::teams::TeamMember::from(
|
||||
data,
|
||||
user.clone(),
|
||||
!logged_in,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let organization = crate::models::organizations::Organization::from(data, team_members);
|
||||
let organization = crate::models::organizations::Organization::from(
|
||||
data,
|
||||
team_members,
|
||||
);
|
||||
organizations.push(organization);
|
||||
}
|
||||
|
||||
@@ -299,9 +319,9 @@ pub async fn user_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
new_user
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
new_user.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
|
||||
|
||||
@@ -313,7 +333,8 @@ pub async fn user_edit(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if let Some(username) = &new_user.username {
|
||||
let existing_user_id_option = User::get(username, &**pool, &redis).await?;
|
||||
let existing_user_id_option =
|
||||
User::get(username, &**pool, &redis).await?;
|
||||
|
||||
if existing_user_id_option
|
||||
.map(|x| UserId::from(x.id))
|
||||
@@ -418,7 +439,8 @@ pub async fn user_edit(
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis)
|
||||
.await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
@@ -460,7 +482,8 @@ pub async fn user_icon_edit(
|
||||
if let Some(actual_user) = id_option {
|
||||
if user.id != actual_user.id.into() && !user.role.is_mod() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to edit this user's icon.".to_string(),
|
||||
"You don't have permission to edit this user's icon."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -471,8 +494,12 @@ pub async fn user_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
262144,
|
||||
"Icons must be smaller than 256KiB",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user_id: UserId = actual_user.id.into();
|
||||
let upload_result = crate::util::img::upload_image_optimized(
|
||||
@@ -572,12 +599,15 @@ pub async fn user_follows(
|
||||
}
|
||||
|
||||
let project_ids = User::get_follows(id, &**pool).await?;
|
||||
let projects: Vec<_> =
|
||||
crate::database::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
let projects: Vec<_> = crate::database::Project::get_many_ids(
|
||||
&project_ids,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::project_creation::{CreateError, UploadedFile};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::loader_fields::{LoaderField, LoaderFieldEnumValue, VersionField};
|
||||
use crate::database::models::loader_fields::{
|
||||
LoaderField, LoaderFieldEnumValue, VersionField,
|
||||
};
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::version_item::{
|
||||
DependencyBuilder, VersionBuilder, VersionFileBuilder,
|
||||
@@ -14,8 +16,8 @@ use crate::models::pack::PackFileHash;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{skip_nulls, DependencyType, ProjectStatus};
|
||||
use crate::models::projects::{
|
||||
Dependency, FileType, Loader, ProjectId, Version, VersionFile, VersionId, VersionStatus,
|
||||
VersionType,
|
||||
Dependency, FileType, Loader, ProjectId, Version, VersionFile, VersionId,
|
||||
VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::teams::ProjectPermissions;
|
||||
use crate::queue::moderation::AutomatedModerationQueue;
|
||||
@@ -122,8 +124,11 @@ pub async fn version_create(
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
let undo_result =
|
||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||
let undo_result = super::project_creation::undo_uploads(
|
||||
&***file_host,
|
||||
&uploaded_files,
|
||||
)
|
||||
.await;
|
||||
let rollback_result = transaction.rollback().await;
|
||||
|
||||
undo_result?;
|
||||
@@ -374,10 +379,12 @@ async fn version_create_inner(
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let version_data = initial_version_data
|
||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||
let builder = version_builder
|
||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||
let version_data = initial_version_data.ok_or_else(|| {
|
||||
CreateError::InvalidInput("`data` field is required".to_string())
|
||||
})?;
|
||||
let builder = version_builder.ok_or_else(|| {
|
||||
CreateError::InvalidInput("`data` field is required".to_string())
|
||||
})?;
|
||||
|
||||
if builder.files.is_empty() {
|
||||
return Err(CreateError::InvalidInput(
|
||||
@@ -470,7 +477,8 @@ async fn version_create_inner(
|
||||
|
||||
for image_id in version_data.uploaded_images {
|
||||
if let Some(db_image) =
|
||||
image_item::Image::get(image_id.into(), &mut **transaction, redis).await?
|
||||
image_item::Image::get(image_id.into(), &mut **transaction, redis)
|
||||
.await?
|
||||
{
|
||||
let image: Image = db_image.into();
|
||||
if !matches!(image.context, ImageContext::Report { .. })
|
||||
@@ -549,8 +557,11 @@ pub async fn upload_file_to_version(
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
let undo_result =
|
||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||
let undo_result = super::project_creation::undo_uploads(
|
||||
&***file_host,
|
||||
&uploaded_files,
|
||||
)
|
||||
.await;
|
||||
let rollback_result = transaction.rollback().await;
|
||||
|
||||
undo_result?;
|
||||
@@ -602,7 +613,8 @@ async fn upload_file_to_version_inner(
|
||||
}
|
||||
};
|
||||
|
||||
let all_loaders = models::loader_fields::Loader::list(&mut **transaction, &redis).await?;
|
||||
let all_loaders =
|
||||
models::loader_fields::Loader::list(&mut **transaction, &redis).await?;
|
||||
let selected_loaders = version
|
||||
.loaders
|
||||
.iter()
|
||||
@@ -615,9 +627,13 @@ async fn upload_file_to_version_inner(
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
if models::Project::get_id(version.inner.project_id, &mut **transaction, &redis)
|
||||
.await?
|
||||
.is_none()
|
||||
if models::Project::get_id(
|
||||
version.inner.project_id,
|
||||
&mut **transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
return Err(CreateError::InvalidInput(
|
||||
"An invalid project id was supplied".to_string(),
|
||||
@@ -633,13 +649,15 @@ async fn upload_file_to_version_inner(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization = Organization::get_associated_organization_project_id(
|
||||
version.inner.project_id,
|
||||
&**client,
|
||||
)
|
||||
.await?;
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(
|
||||
version.inner.project_id,
|
||||
&**client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
let organization_team_member = if let Some(organization) = &organization
|
||||
{
|
||||
models::TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
user.id.into(),
|
||||
@@ -659,7 +677,8 @@ async fn upload_file_to_version_inner(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::UPLOAD_VERSION) {
|
||||
return Err(CreateError::CustomAuthenticationError(
|
||||
"You don't have permission to upload files to this version!".to_string(),
|
||||
"You don't have permission to upload files to this version!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -676,7 +695,9 @@ async fn upload_file_to_version_inner(
|
||||
let result = async {
|
||||
let content_disposition = field.content_disposition().clone();
|
||||
let name = content_disposition.get_name().ok_or_else(|| {
|
||||
CreateError::MissingValueError("Missing content name".to_string())
|
||||
CreateError::MissingValueError(
|
||||
"Missing content name".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if name == "data" {
|
||||
@@ -691,7 +712,9 @@ async fn upload_file_to_version_inner(
|
||||
}
|
||||
|
||||
let file_data = initial_file_data.as_ref().ok_or_else(|| {
|
||||
CreateError::InvalidInput(String::from("`data` field must come before file fields"))
|
||||
CreateError::InvalidInput(String::from(
|
||||
"`data` field must come before file fields",
|
||||
))
|
||||
})?;
|
||||
|
||||
let loaders = selected_loaders
|
||||
@@ -788,7 +811,8 @@ pub async fn upload_file(
|
||||
|
||||
if other_file_names.contains(&format!("{}.{}", file_name, file_extension)) {
|
||||
return Err(CreateError::InvalidInput(
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -799,7 +823,9 @@ pub async fn upload_file(
|
||||
}
|
||||
|
||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
CreateError::InvalidFileType(file_extension.to_string())
|
||||
})?;
|
||||
|
||||
let data = read_from_field(
|
||||
field, 500 * (1 << 20),
|
||||
@@ -825,7 +851,8 @@ pub async fn upload_file(
|
||||
|
||||
if exists {
|
||||
return Err(CreateError::InvalidInput(
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -867,7 +894,11 @@ pub async fn upload_file(
|
||||
|
||||
for file in &format.files {
|
||||
if let Some(dep) = res.iter().find(|x| {
|
||||
Some(&*x.hash) == file.hashes.get(&PackFileHash::Sha1).map(|x| x.as_bytes())
|
||||
Some(&*x.hash)
|
||||
== file
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| x.as_bytes())
|
||||
}) {
|
||||
dependencies.push(DependencyBuilder {
|
||||
project_id: Some(models::ProjectId(dep.project_id)),
|
||||
@@ -917,7 +948,8 @@ pub async fn upload_file(
|
||||
version_id,
|
||||
urlencoding::encode(file_name)
|
||||
);
|
||||
let file_path = format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||
let file_path =
|
||||
format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &file_path, data)
|
||||
@@ -937,7 +969,8 @@ pub async fn upload_file(
|
||||
.any(|y| y.hash == sha1_bytes || y.hash == sha512_bytes)
|
||||
}) {
|
||||
return Err(CreateError::InvalidInput(
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -975,9 +1008,9 @@ pub async fn upload_file(
|
||||
pub fn get_name_ext(
|
||||
content_disposition: &actix_web::http::header::ContentDisposition,
|
||||
) -> Result<(&str, &str), CreateError> {
|
||||
let file_name = content_disposition
|
||||
.get_filename()
|
||||
.ok_or_else(|| CreateError::MissingValueError("Missing content file name".to_string()))?;
|
||||
let file_name = content_disposition.get_filename().ok_or_else(|| {
|
||||
CreateError::MissingValueError("Missing content file name".to_string())
|
||||
})?;
|
||||
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
||||
file_name.get((last_period + 1)..).unwrap_or("")
|
||||
} else {
|
||||
@@ -994,7 +1027,10 @@ 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>>,
|
||||
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
|
||||
|
||||
@@ -65,13 +65,18 @@ pub async fn get_version_from_hash(
|
||||
)
|
||||
.await?;
|
||||
if let Some(file) = file {
|
||||
let version = database::models::Version::get(file.version_id, &**pool, &redis).await?;
|
||||
let version =
|
||||
database::models::Version::get(file.version_id, &**pool, &redis)
|
||||
.await?;
|
||||
if let Some(version) = version {
|
||||
if !is_visible_version(&version.inner, &user_option, &pool, &redis).await? {
|
||||
if !is_visible_version(&version.inner, &user_option, &pool, &redis)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(version)))
|
||||
Ok(HttpResponse::Ok()
|
||||
.json(models::projects::Version::from(version)))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
@@ -147,42 +152,59 @@ pub async fn get_update_from_hash(
|
||||
.await?
|
||||
{
|
||||
if let Some(project) =
|
||||
database::models::Project::get_id(file.project_id, &**pool, &redis).await?
|
||||
{
|
||||
let versions = database::models::Version::get_many(&project.versions, &**pool, &redis)
|
||||
database::models::Project::get_id(file.project_id, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| {
|
||||
let mut bool = true;
|
||||
if let Some(version_types) = &update_data.version_types {
|
||||
bool &= version_types
|
||||
{
|
||||
let versions = database::models::Version::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|x| {
|
||||
let mut bool = true;
|
||||
if let Some(version_types) = &update_data.version_types {
|
||||
bool &= version_types
|
||||
.iter()
|
||||
.any(|y| y.as_str() == x.inner.version_type);
|
||||
}
|
||||
if let Some(loaders) = &update_data.loaders {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
}
|
||||
if let Some(loader_fields) = &update_data.loader_fields {
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) = x
|
||||
.version_fields
|
||||
.iter()
|
||||
.any(|y| y.as_str() == x.inner.version_type);
|
||||
.find(|y| y.field_name == *key)
|
||||
{
|
||||
values
|
||||
.iter()
|
||||
.any(|v| x_vf.value.contains_json_value(v))
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
if let Some(loaders) = &update_data.loaders {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
}
|
||||
if let Some(loader_fields) = &update_data.loader_fields {
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) =
|
||||
x.version_fields.iter().find(|y| y.field_name == *key)
|
||||
{
|
||||
values.iter().any(|v| x_vf.value.contains_json_value(v))
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
}
|
||||
bool
|
||||
})
|
||||
.sorted();
|
||||
}
|
||||
bool
|
||||
})
|
||||
.sorted();
|
||||
|
||||
if let Some(first) = versions.last() {
|
||||
if !is_visible_version(&first.inner, &user_option, &pool, &redis).await? {
|
||||
if !is_visible_version(
|
||||
&first.inner,
|
||||
&user_option,
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Ok().json(models::projects::Version::from(first)));
|
||||
return Ok(HttpResponse::Ok()
|
||||
.json(models::projects::Version::from(first)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,7 +251,8 @@ pub async fn get_versions_from_hashes(
|
||||
|
||||
let version_ids = files.iter().map(|x| x.version_id).collect::<Vec<_>>();
|
||||
let versions_data = filter_visible_versions(
|
||||
database::models::Version::get_many(&version_ids, &**pool, &redis).await?,
|
||||
database::models::Version::get_many(&version_ids, &**pool, &redis)
|
||||
.await?,
|
||||
&user_option,
|
||||
&pool,
|
||||
&redis,
|
||||
@@ -282,7 +305,8 @@ pub async fn get_projects_from_hashes(
|
||||
let project_ids = files.iter().map(|x| x.project_id).collect::<Vec<_>>();
|
||||
|
||||
let projects_data = filter_visible_projects(
|
||||
database::models::Project::get_many_ids(&project_ids, &**pool, &redis).await?,
|
||||
database::models::Project::get_many_ids(&project_ids, &**pool, &redis)
|
||||
.await?,
|
||||
&user_option,
|
||||
&pool,
|
||||
false,
|
||||
@@ -456,28 +480,41 @@ 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(&algorithm) {
|
||||
if let Some(query_file) = update_data.hashes.iter().find(|x| &x.hash == hash) {
|
||||
if let Some(query_file) =
|
||||
update_data.hashes.iter().find(|x| &x.hash == hash)
|
||||
{
|
||||
let version = all_versions
|
||||
.iter()
|
||||
.filter(|x| x.inner.project_id == file.project_id)
|
||||
.filter(|x| {
|
||||
let mut bool = true;
|
||||
|
||||
if let Some(version_types) = &query_file.version_types {
|
||||
bool &= version_types
|
||||
.iter()
|
||||
.any(|y| y.as_str() == x.inner.version_type);
|
||||
if let Some(version_types) =
|
||||
&query_file.version_types
|
||||
{
|
||||
bool &= version_types.iter().any(|y| {
|
||||
y.as_str() == x.inner.version_type
|
||||
});
|
||||
}
|
||||
if let Some(loaders) = &query_file.loaders {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
bool &= x
|
||||
.loaders
|
||||
.iter()
|
||||
.any(|y| loaders.contains(y));
|
||||
}
|
||||
|
||||
if let Some(loader_fields) = &query_file.loader_fields {
|
||||
if let Some(loader_fields) =
|
||||
&query_file.loader_fields
|
||||
{
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) =
|
||||
x.version_fields.iter().find(|y| y.field_name == *key)
|
||||
bool &= if let Some(x_vf) = x
|
||||
.version_fields
|
||||
.iter()
|
||||
.find(|y| y.field_name == *key)
|
||||
{
|
||||
values.iter().any(|v| x_vf.value.contains_json_value(v))
|
||||
values.iter().any(|v| {
|
||||
x_vf.value.contains_json_value(v)
|
||||
})
|
||||
} else {
|
||||
true
|
||||
};
|
||||
@@ -489,10 +526,19 @@ pub async fn update_individual_files(
|
||||
.last();
|
||||
|
||||
if let Some(version) = version {
|
||||
if is_visible_version(&version.inner, &user_option, &pool, &redis).await? {
|
||||
if is_visible_version(
|
||||
&version.inner,
|
||||
&user_option,
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
response.insert(
|
||||
hash.clone(),
|
||||
models::projects::Version::from(version.clone()),
|
||||
models::projects::Version::from(
|
||||
version.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -539,13 +585,14 @@ pub async fn delete_file(
|
||||
|
||||
if let Some(row) = file {
|
||||
if !user.role.is_admin() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||
row.version_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_version(
|
||||
row.version_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
let organization =
|
||||
database::models::Organization::get_associated_organization_project_id(
|
||||
@@ -555,18 +602,19 @@ pub async fn delete_file(
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let organization_team_member =
|
||||
if let Some(organization) = &organization {
|
||||
database::models::TeamMember::get_from_user_id_organization(
|
||||
organization.id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let permissions = ProjectPermissions::get_permissions_by_role(
|
||||
&user.role,
|
||||
@@ -577,16 +625,20 @@ pub async fn delete_file(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::DELETE_VERSION) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You don't have permission to delete this file!".to_string(),
|
||||
"You don't have permission to delete this file!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let version = database::models::Version::get(row.version_id, &**pool, &redis).await?;
|
||||
let version =
|
||||
database::models::Version::get(row.version_id, &**pool, &redis)
|
||||
.await?;
|
||||
if let Some(version) = version {
|
||||
if version.files.len() < 2 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Versions must have at least one file uploaded to them".to_string(),
|
||||
"Versions must have at least one file uploaded to them"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -663,10 +715,14 @@ pub async fn download_version(
|
||||
.await?;
|
||||
|
||||
if let Some(file) = file {
|
||||
let version = database::models::Version::get(file.version_id, &**pool, &redis).await?;
|
||||
let version =
|
||||
database::models::Version::get(file.version_id, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
if let Some(version) = version {
|
||||
if !is_visible_version(&version.inner, &user_option, &pool, &redis).await? {
|
||||
if !is_visible_version(&version.inner, &user_option, &pool, &redis)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::ApiError;
|
||||
use crate::auth::checks::{filter_visible_versions, is_visible_project, is_visible_version};
|
||||
use crate::auth::checks::{
|
||||
filter_visible_versions, is_visible_project, is_visible_version,
|
||||
};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
use crate::database::models::loader_fields::{
|
||||
@@ -16,7 +18,9 @@ use crate::models::ids::VersionId;
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{skip_nulls, Loader};
|
||||
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
||||
use crate::models::projects::{
|
||||
Dependency, FileType, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::teams::ProjectPermissions;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::search::indexing::remove_documents;
|
||||
@@ -80,21 +84,31 @@ pub async fn version_project_get_helper(
|
||||
.ok();
|
||||
|
||||
if let Some(project) = result {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false).await? {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let versions =
|
||||
database::models::Version::get_many(&project.versions, &**pool, &redis).await?;
|
||||
let versions = database::models::Version::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let id_opt = parse_base62(&id.1).ok();
|
||||
let version = versions
|
||||
.into_iter()
|
||||
.find(|x| Some(x.inner.id.0 as u64) == id_opt || x.inner.version_number == id.1);
|
||||
let version = versions.into_iter().find(|x| {
|
||||
Some(x.inner.id.0 as u64) == id_opt
|
||||
|| x.inner.version_number == id.1
|
||||
});
|
||||
|
||||
if let Some(version) = version {
|
||||
if is_visible_version(&version.inner, &user_option, &pool, &redis).await? {
|
||||
return Ok(HttpResponse::Ok().json(models::projects::Version::from(version)));
|
||||
if is_visible_version(&version.inner, &user_option, &pool, &redis)
|
||||
.await?
|
||||
{
|
||||
return Ok(HttpResponse::Ok()
|
||||
.json(models::projects::Version::from(version)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,11 +128,14 @@ pub async fn versions_get(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<database::models::VersionId>>();
|
||||
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
|
||||
let version_ids =
|
||||
serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<database::models::VersionId>>();
|
||||
let versions_data =
|
||||
database::models::Version::get_many(&version_ids, &**pool, &redis)
|
||||
.await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -131,7 +148,9 @@ pub async fn versions_get(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
let versions = filter_visible_versions(versions_data, &user_option, &pool, &redis).await?;
|
||||
let versions =
|
||||
filter_visible_versions(versions_data, &user_option, &pool, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(versions))
|
||||
}
|
||||
@@ -154,7 +173,8 @@ pub async fn version_get_helper(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
|
||||
let version_data =
|
||||
database::models::Version::get(id.into(), &**pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -169,7 +189,9 @@ pub async fn version_get_helper(
|
||||
|
||||
if let Some(data) = version_data {
|
||||
if is_visible_version(&data.inner, &user_option, &pool, &redis).await? {
|
||||
return Ok(HttpResponse::Ok().json(models::projects::Version::from(data)));
|
||||
return Ok(
|
||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +253,8 @@ pub async fn version_edit(
|
||||
new_version: web::Json<serde_json::Value>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let new_version: EditVersion = serde_json::from_value(new_version.into_inner())?;
|
||||
let new_version: EditVersion =
|
||||
serde_json::from_value(new_version.into_inner())?;
|
||||
version_edit_helper(
|
||||
req,
|
||||
info.into_inner(),
|
||||
@@ -260,9 +283,9 @@ pub async fn version_edit_helper(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
new_version
|
||||
.validate()
|
||||
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||
new_version.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let version_id = info.0;
|
||||
let id = version_id.into();
|
||||
@@ -270,21 +293,24 @@ pub async fn version_edit_helper(
|
||||
let result = database::models::Version::get(id, &**pool, &redis).await?;
|
||||
|
||||
if let Some(version_item) = result {
|
||||
let team_member = database::models::TeamMember::get_from_user_id_project(
|
||||
version_item.inner.project_id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_project(
|
||||
version_item.inner.project_id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization = Organization::get_associated_organization_project_id(
|
||||
version_item.inner.project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(
|
||||
version_item.inner.project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
let organization_team_member = if let Some(organization) = &organization
|
||||
{
|
||||
database::models::TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
user.id.into(),
|
||||
@@ -304,7 +330,8 @@ pub async fn version_edit_helper(
|
||||
if let Some(perms) = permissions {
|
||||
if !perms.contains(ProjectPermissions::UPLOAD_VERSION) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit this version!".to_string(),
|
||||
"You do not have the permissions to edit this version!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -372,8 +399,12 @@ pub async fn version_edit_helper(
|
||||
})
|
||||
.collect::<Vec<database::models::version_item::DependencyBuilder>>();
|
||||
|
||||
DependencyBuilder::insert_many(builders, version_item.inner.id, &mut transaction)
|
||||
.await?;
|
||||
DependencyBuilder::insert_many(
|
||||
builders,
|
||||
version_item.inner.id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if !new_version.fields.is_empty() {
|
||||
@@ -383,20 +414,34 @@ pub async fn version_edit_helper(
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let all_loaders = loader_fields::Loader::list(&mut *transaction, &redis).await?;
|
||||
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))
|
||||
.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))
|
||||
.collect::<Vec<LoaderField>>();
|
||||
let loader_fields = LoaderField::get_fields(
|
||||
&loader_ids,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|lf| version_fields_names.contains(&lf.field))
|
||||
.collect::<Vec<LoaderField>>();
|
||||
|
||||
let loader_field_ids = loader_fields.iter().map(|lf| lf.id.0).collect::<Vec<i32>>();
|
||||
let loader_field_ids = loader_fields
|
||||
.iter()
|
||||
.map(|lf| lf.id.0)
|
||||
.collect::<Vec<i32>>();
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM version_fields
|
||||
@@ -409,12 +454,13 @@ pub async fn version_edit_helper(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let mut loader_field_enum_values = LoaderFieldEnumValue::list_many_loader_fields(
|
||||
&loader_fields,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let mut loader_field_enum_values =
|
||||
LoaderFieldEnumValue::list_many_loader_fields(
|
||||
&loader_fields,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut version_fields = Vec::new();
|
||||
for (vf_name, vf_value) in new_version.fields {
|
||||
@@ -438,7 +484,8 @@ pub async fn version_edit_helper(
|
||||
.map_err(ApiError::InvalidInput)?;
|
||||
version_fields.push(vf);
|
||||
}
|
||||
VersionField::insert_many(version_fields, &mut transaction).await?;
|
||||
VersionField::insert_many(version_fields, &mut transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(loaders) = &new_version.loaders {
|
||||
@@ -453,18 +500,23 @@ pub async fn version_edit_helper(
|
||||
|
||||
let mut loader_versions = Vec::new();
|
||||
for loader in loaders {
|
||||
let loader_id = database::models::loader_fields::Loader::get_id(
|
||||
&loader.0,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("No database entry for loader provided.".to_string())
|
||||
})?;
|
||||
let loader_id =
|
||||
database::models::loader_fields::Loader::get_id(
|
||||
&loader.0,
|
||||
&mut *transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"No database entry for loader provided."
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
loader_versions.push(LoaderVersion::new(loader_id, id));
|
||||
}
|
||||
LoaderVersion::insert_many(loader_versions, &mut transaction).await?;
|
||||
LoaderVersion::insert_many(loader_versions, &mut transaction)
|
||||
.await?;
|
||||
|
||||
crate::database::models::Project::clear_cache(
|
||||
version_item.inner.project_id,
|
||||
@@ -531,7 +583,8 @@ pub async fn version_edit_helper(
|
||||
WHERE (id = $2)
|
||||
",
|
||||
diff as i32,
|
||||
version_item.inner.project_id as database::models::ids::ProjectId,
|
||||
version_item.inner.project_id
|
||||
as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
@@ -614,10 +667,17 @@ pub async fn version_edit_helper(
|
||||
version_id: Some(version_item.inner.id.into()),
|
||||
};
|
||||
|
||||
img::delete_unused_images(context, checkable_strings, &mut transaction, &redis).await?;
|
||||
img::delete_unused_images(
|
||||
context,
|
||||
checkable_strings,
|
||||
&mut transaction,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Version::clear_cache(&version_item, &redis).await?;
|
||||
database::models::Version::clear_cache(&version_item, &redis)
|
||||
.await?;
|
||||
database::models::Project::clear_cache(
|
||||
version_item.inner.project_id,
|
||||
None,
|
||||
@@ -662,7 +722,8 @@ pub async fn version_list(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = database::models::Project::get(&string, &**pool, &redis).await?;
|
||||
let result =
|
||||
database::models::Project::get(&string, &**pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -676,45 +737,51 @@ pub async fn version_list(
|
||||
.ok();
|
||||
|
||||
if let Some(project) = result {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false).await? {
|
||||
if !is_visible_project(&project.inner, &user_option, &pool, false)
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
let loader_field_filters = filters.loader_fields.as_ref().map(|x| {
|
||||
serde_json::from_str::<HashMap<String, Vec<serde_json::Value>>>(x).unwrap_or_default()
|
||||
serde_json::from_str::<HashMap<String, Vec<serde_json::Value>>>(x)
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let loader_filters = filters
|
||||
.loaders
|
||||
.as_ref()
|
||||
.map(|x| serde_json::from_str::<Vec<String>>(x).unwrap_or_default());
|
||||
let mut versions = database::models::Version::get_many(&project.versions, &**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.skip(filters.offset.unwrap_or(0))
|
||||
.take(filters.limit.unwrap_or(usize::MAX))
|
||||
.filter(|x| {
|
||||
let mut bool = true;
|
||||
let loader_filters = filters.loaders.as_ref().map(|x| {
|
||||
serde_json::from_str::<Vec<String>>(x).unwrap_or_default()
|
||||
});
|
||||
let mut versions = database::models::Version::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.skip(filters.offset.unwrap_or(0))
|
||||
.take(filters.limit.unwrap_or(usize::MAX))
|
||||
.filter(|x| {
|
||||
let mut bool = true;
|
||||
|
||||
if let Some(version_type) = filters.version_type {
|
||||
bool &= &*x.inner.version_type == version_type.as_str();
|
||||
if let Some(version_type) = filters.version_type {
|
||||
bool &= &*x.inner.version_type == version_type.as_str();
|
||||
}
|
||||
if let Some(loaders) = &loader_filters {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
}
|
||||
if let Some(loader_fields) = &loader_field_filters {
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) =
|
||||
x.version_fields.iter().find(|y| y.field_name == *key)
|
||||
{
|
||||
values.iter().any(|v| x_vf.value.contains_json_value(v))
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
if let Some(loaders) = &loader_filters {
|
||||
bool &= x.loaders.iter().any(|y| loaders.contains(y));
|
||||
}
|
||||
if let Some(loader_fields) = &loader_field_filters {
|
||||
for (key, values) in loader_fields {
|
||||
bool &= if let Some(x_vf) =
|
||||
x.version_fields.iter().find(|y| y.field_name == *key)
|
||||
{
|
||||
values.iter().any(|v| x_vf.value.contains_json_value(v))
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
}
|
||||
bool
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
bool
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut response = versions
|
||||
.iter()
|
||||
@@ -727,10 +794,15 @@ pub async fn version_list(
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||
versions.sort_by(|a, b| {
|
||||
b.inner.date_published.cmp(&a.inner.date_published)
|
||||
});
|
||||
|
||||
// Attempt to populate versions with "auto featured" versions
|
||||
if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) {
|
||||
if response.is_empty()
|
||||
&& !versions.is_empty()
|
||||
&& filters.featured.unwrap_or(false)
|
||||
{
|
||||
// TODO: This is a bandaid fix for detecting auto-featured versions.
|
||||
// In the future, not all versions will have 'game_versions' fields, so this will need to be changed.
|
||||
let (loaders, game_versions) = futures::future::try_join(
|
||||
@@ -777,10 +849,14 @@ pub async fn version_list(
|
||||
}
|
||||
}
|
||||
|
||||
response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||
response.sort_by(|a, b| {
|
||||
b.inner.date_published.cmp(&a.inner.date_published)
|
||||
});
|
||||
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
||||
|
||||
let response = filter_visible_versions(response, &user_option, &pool, &redis).await?;
|
||||
let response =
|
||||
filter_visible_versions(response, &user_option, &pool, &redis)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
@@ -810,24 +886,31 @@ pub async fn version_delete(
|
||||
let version = database::models::Version::get(id.into(), &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput("The specified version does not exist!".to_string())
|
||||
ApiError::InvalidInput(
|
||||
"The specified version does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_admin() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id_project(
|
||||
version.inner.project_id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_project(
|
||||
version.inner.project_id,
|
||||
user.id.into(),
|
||||
false,
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::Database)?;
|
||||
|
||||
let organization =
|
||||
Organization::get_associated_organization_project_id(version.inner.project_id, &**pool)
|
||||
.await?;
|
||||
Organization::get_associated_organization_project_id(
|
||||
version.inner.project_id,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let organization_team_member = if let Some(organization) = &organization {
|
||||
let organization_team_member = if let Some(organization) = &organization
|
||||
{
|
||||
database::models::TeamMember::get_from_user_id(
|
||||
organization.team_id,
|
||||
user.id.into(),
|
||||
@@ -846,7 +929,8 @@ pub async fn version_delete(
|
||||
|
||||
if !permissions.contains(ProjectPermissions::DELETE_VERSION) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to delete versions in this team".to_string(),
|
||||
"You do not have permission to delete versions in this team"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -856,17 +940,27 @@ pub async fn version_delete(
|
||||
version_id: Some(version.inner.id.into()),
|
||||
};
|
||||
let uploaded_images =
|
||||
database::models::Image::get_many_contexted(context, &mut transaction).await?;
|
||||
database::models::Image::get_many_contexted(context, &mut transaction)
|
||||
.await?;
|
||||
for image in uploaded_images {
|
||||
image_item::Image::remove(image.id, &mut transaction, &redis).await?;
|
||||
}
|
||||
|
||||
let result =
|
||||
database::models::Version::remove_full(version.inner.id, &redis, &mut transaction).await?;
|
||||
let result = database::models::Version::remove_full(
|
||||
version.inner.id,
|
||||
&redis,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
remove_documents(&[version.inner.id.into()], &search_config).await?;
|
||||
database::models::Project::clear_cache(version.inner.project_id, None, Some(true), &redis)
|
||||
.await?;
|
||||
database::models::Project::clear_cache(
|
||||
version.inner.project_id,
|
||||
None,
|
||||
Some(true),
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
Reference in New Issue
Block a user