You've already forked AstralRinth
forked from didirus/AstralRinth
Testing bug fixes (#788)
* fixes * adds tests- fixes failures * changes * moved transaction commits/caches around * collections nullable * merge fixes * sqlx prepare * revs * lf fixes * made changes back * added collections update --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
14
.sqlx/query-62378074f2f12d010b4b2139ac8c879b6cb54517aaf36c55b6f99f1604015bb7.json
generated
Normal file
14
.sqlx/query-62378074f2f12d010b4b2139ac8c879b6cb54517aaf36c55b6f99f1604015bb7.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE collections\n SET updated = NOW()\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "62378074f2f12d010b4b2139ac8c879b6cb54517aaf36c55b6f99f1604015bb7"
|
||||
}
|
||||
22
.sqlx/query-902d0803deb5eca7614f3a68ccae6c3b401fcaa0bcc304b9caf18afc20a3e52b.json
generated
Normal file
22
.sqlx/query-902d0803deb5eca7614f3a68ccae6c3b401fcaa0bcc304b9caf18afc20a3e52b.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT t.id\n FROM threads t\n INNER JOIN reports r ON t.report_id = r.id\n WHERE r.mod_id = $1 AND report_id IS NOT NULL \n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "902d0803deb5eca7614f3a68ccae6c3b401fcaa0bcc304b9caf18afc20a3e52b"
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
|
||||
16
migrations/20231205095400_remaining_loader_field_loaders.sql
Normal file
16
migrations/20231205095400_remaining_loader_field_loaders.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Adds loader_fields_loaders entries for all loaders
|
||||
-- (at this point, they are all Minecraft loaders, and thus have the same fields)
|
||||
-- These are loaders such as bukkit, minecraft, vanilla, waterfall, velocity... etc
|
||||
-- This also allows v2 routes (which have things such as client_side to remain to work with these loaders)
|
||||
INSERT INTO loader_fields_loaders
|
||||
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf
|
||||
WHERE lf.field=ANY(ARRAY['game_versions','client_and_server','server_only','client_only','singleplayer'])
|
||||
AND
|
||||
l.loader NOT IN ('vanilla', 'minecraft', 'optifine', 'iris', 'canvas')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- All existing loader_project_types so far should have a games entry as minecraft
|
||||
INSERT INTO loaders_project_types_games
|
||||
SELECT lpt.joining_loader_id, lpt.joining_project_type_id, g.id FROM loaders_project_types lpt CROSS JOIN games g
|
||||
WHERE g.name='minecraft-java'
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE collections ALTER COLUMN description DROP NOT NULL;
|
||||
@@ -13,7 +13,7 @@ pub struct CollectionBuilder {
|
||||
pub collection_id: CollectionId,
|
||||
pub user_id: UserId,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub description: Option<String>,
|
||||
pub status: CollectionStatus,
|
||||
pub projects: Vec<ProjectId>,
|
||||
}
|
||||
@@ -45,7 +45,7 @@ pub struct Collection {
|
||||
pub id: CollectionId,
|
||||
pub user_id: UserId,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub description: Option<String>,
|
||||
pub created: DateTime<Utc>,
|
||||
pub updated: DateTime<Utc>,
|
||||
pub icon_url: Option<String>,
|
||||
@@ -73,7 +73,7 @@ impl Collection {
|
||||
self.id as CollectionId,
|
||||
self.user_id as UserId,
|
||||
&self.name,
|
||||
&self.description,
|
||||
self.description.as_ref(),
|
||||
self.created,
|
||||
self.icon_url.as_ref(),
|
||||
self.status.to_string(),
|
||||
|
||||
@@ -344,6 +344,28 @@ impl Project {
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
// Notably joins with report id and not thread.mod_id directly, as
|
||||
// this is set to null for threads that are reports.
|
||||
let report_threads = sqlx::query!(
|
||||
"
|
||||
SELECT t.id
|
||||
FROM threads t
|
||||
INNER JOIN reports r ON t.report_id = r.id
|
||||
WHERE r.mod_id = $1 AND report_id IS NOT NULL
|
||||
",
|
||||
id as ProjectId,
|
||||
)
|
||||
.fetch_many(&mut **transaction)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|x| ThreadId(x.id))) })
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
for thread_id in report_threads {
|
||||
models::Thread::remove_full(thread_id, transaction).await?;
|
||||
}
|
||||
|
||||
models::Thread::remove_full(project.thread_id, transaction).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM reports
|
||||
@@ -398,8 +420,6 @@ impl Project {
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
models::Thread::remove_full(project.thread_id, transaction).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM mods
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::models::projects::{
|
||||
ProjectStatus, Version, VersionFile, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::threads::ThreadId;
|
||||
use crate::routes::v2_reroute;
|
||||
use crate::routes::v2_reroute::{self, capitalize_first};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -395,11 +395,3 @@ impl TryFrom<Link> for DonationLink {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn capitalize_first(input: &str) -> String {
|
||||
let mut result = input.to_owned();
|
||||
if let Some(first_char) = result.get_mut(0..1) {
|
||||
first_char.make_ascii_uppercase();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct Collection {
|
||||
/// The title or name of the collection.
|
||||
pub name: String,
|
||||
/// A short description of the collection.
|
||||
pub description: String,
|
||||
pub description: Option<String>,
|
||||
|
||||
/// An icon URL for the collection.
|
||||
pub icon_url: Option<String>,
|
||||
|
||||
@@ -720,6 +720,8 @@ pub async fn process_payout(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
if !clear_cache_users.is_empty() {
|
||||
crate::database::models::User::clear_caches(
|
||||
&clear_cache_users
|
||||
@@ -731,8 +733,6 @@ pub async fn process_payout(
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,10 @@ impl AuthQueue {
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
PersonalAccessToken::clear_cache(clear_cache_pats, redis).await?;
|
||||
|
||||
update_oauth_access_token_last_used(oauth_access_token_queue, &mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
PersonalAccessToken::clear_cache(clear_cache_pats, redis).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1195,8 +1195,8 @@ pub async fn auth_callback(
|
||||
)?;
|
||||
}
|
||||
|
||||
crate::database::models::User::clear_caches(&[(id, None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(id, None)], &redis).await?;
|
||||
|
||||
if let Some(url) = url {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
@@ -1395,8 +1395,8 @@ pub async fn delete_auth_provider(
|
||||
}
|
||||
}
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -1864,8 +1864,8 @@ pub async fn finish_2fa_flow(
|
||||
)?;
|
||||
}
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"backup_codes": codes,
|
||||
@@ -1952,8 +1952,8 @@ pub async fn remove_2fa(
|
||||
)?;
|
||||
}
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -2138,8 +2138,8 @@ pub async fn change_password(
|
||||
)?;
|
||||
}
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
@@ -2210,8 +2210,8 @@ pub async fn set_email(
|
||||
"We need to verify your email address.",
|
||||
)?;
|
||||
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
@@ -2300,8 +2300,8 @@ pub async fn verify_email(
|
||||
.await?;
|
||||
|
||||
Flow::remove(&email.flow, &redis).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
} else {
|
||||
|
||||
@@ -127,12 +127,12 @@ pub async fn create_pat(
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||
vec![(None, None, Some(user.id.into()))],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
||||
id: id.into(),
|
||||
@@ -232,12 +232,12 @@ pub async fn edit_pat(
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,12 +269,12 @@ pub async fn delete_pat(
|
||||
let mut transaction = pool.begin().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))],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@ pub async fn delete(
|
||||
if session.user_id == current_user.id.into() {
|
||||
let mut transaction = pool.begin().await?;
|
||||
DBSession::remove(session.id, &mut transaction).await?;
|
||||
transaction.commit().await?;
|
||||
DBSession::clear_cache(
|
||||
vec![(
|
||||
Some(session.id),
|
||||
@@ -196,7 +197,6 @@ pub async fn delete(
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ pub async fn refresh(
|
||||
|
||||
DBSession::remove(session.id, &mut transaction).await?;
|
||||
let new_session = issue_session(req, session.user_id, &mut transaction, &redis).await?;
|
||||
transaction.commit().await?;
|
||||
DBSession::clear_cache(
|
||||
vec![(
|
||||
Some(session.id),
|
||||
@@ -242,8 +243,6 @@ pub async fn refresh(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(Session::from(new_session, true, None)))
|
||||
} else {
|
||||
Err(ApiError::Authentication(
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::ApiError;
|
||||
use crate::database::models::loader_fields::LoaderFieldEnumValue;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::v2::projects::LegacySideType;
|
||||
use crate::routes::v2_reroute::capitalize_first;
|
||||
use crate::routes::v3::tags::{
|
||||
LinkPlatformQueryData, LoaderData as LoaderDataV3, LoaderFieldsEnumQuery,
|
||||
};
|
||||
@@ -172,11 +173,12 @@ pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse,
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct DonationPlatformQueryData {
|
||||
// The difference between name and short is removed in v3.
|
||||
// Now, the 'id' becomes the name, and the 'name' is removed (the frontend uses the id as the name)
|
||||
// pub short: String,
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
@@ -193,7 +195,26 @@ pub async fn donation_platform_list(
|
||||
Ok(platforms) => {
|
||||
let platforms = platforms
|
||||
.into_iter()
|
||||
.map(|p| DonationPlatformQueryData { name: p.name })
|
||||
.filter_map(|p| {
|
||||
if p.donation {
|
||||
Some(DonationPlatformQueryData {
|
||||
// Short vs name is no longer a recognized difference in v3.
|
||||
// We capitalize to recreate the old behavior, with some special handling.
|
||||
// This may result in different behaviour for platforms added after the v3 migration.
|
||||
name: match p.name.as_str() {
|
||||
"bmac" => "Buy Me A Coffee".to_string(),
|
||||
"github" => "GitHub Sponsors".to_string(),
|
||||
"ko-fi" => "Ko-fi".to_string(),
|
||||
"paypal" => "PayPal".to_string(),
|
||||
// Otherwise, capitalize it
|
||||
_ => capitalize_first(&p.name),
|
||||
},
|
||||
short: p.name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
HttpResponse::Ok().json(platforms)
|
||||
}
|
||||
|
||||
@@ -100,34 +100,66 @@ pub async fn version_create(
|
||||
json!(legacy_create.game_versions),
|
||||
);
|
||||
|
||||
// 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) => match v2_reroute::extract_ok_json::<
|
||||
Vec<v3::tags::LoaderData>,
|
||||
>(loader_response)
|
||||
.await
|
||||
{
|
||||
Ok(loaders) => loaders,
|
||||
Err(_) => vec![],
|
||||
},
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
let loader_fields_aggregate = loaders
|
||||
.into_iter()
|
||||
.filter_map(|loader| {
|
||||
if legacy_create.loaders.contains(&Loader(loader.name.clone())) {
|
||||
Some(loader.supported_fields)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Copies side types of another version of the project.
|
||||
// If no version exists, defaults to all false.
|
||||
// TODO: write test for this to ensure predictible unchanging behaviour
|
||||
// This is inherently lossy, but not much can be done about it, as side types are no longer associated with projects,
|
||||
// so the 'missing' ones can't be easily accessed.
|
||||
// so the 'missing' ones can't be easily accessed, and versions do need to have these fields explicitly set.
|
||||
let side_type_loader_field_names = [
|
||||
"singleplayer",
|
||||
"client_and_server",
|
||||
"client_only",
|
||||
"server_only",
|
||||
];
|
||||
fields.extend(
|
||||
side_type_loader_field_names
|
||||
.iter()
|
||||
.map(|f| (f.to_string(), json!(false))),
|
||||
);
|
||||
if let Some(example_version_fields) =
|
||||
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
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Check if loader_fields_aggregate contains any of these side types
|
||||
// We assume these four fields are linked together.
|
||||
if loader_fields_aggregate
|
||||
.iter()
|
||||
.any(|f| side_type_loader_field_names.contains(&f.as_str()))
|
||||
{
|
||||
// If so, we get the fields of the example version of the project, and set the side types to match.
|
||||
fields.extend(
|
||||
side_type_loader_field_names
|
||||
.iter()
|
||||
.map(|f| (f.to_string(), json!(false))),
|
||||
);
|
||||
if let Some(example_version_fields) =
|
||||
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
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Handle project type via file extension prediction
|
||||
let mut project_type = None;
|
||||
for file_part in &legacy_create.file_parts {
|
||||
|
||||
@@ -300,6 +300,14 @@ pub fn convert_side_types_v2_bools(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capitalize_first(input: &str) -> String {
|
||||
let mut result = input.to_owned();
|
||||
if let Some(first_char) = result.get_mut(0..1) {
|
||||
first_char.make_ascii_uppercase();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -46,7 +46,7 @@ pub struct CollectionCreateData {
|
||||
pub name: String,
|
||||
#[validate(length(min = 3, max = 255))]
|
||||
/// A short description of the collection.
|
||||
pub description: String,
|
||||
pub description: Option<String>,
|
||||
#[validate(length(max = 32))]
|
||||
#[serde(default = "Vec::new")]
|
||||
/// A list of initial projects to use with the created collection
|
||||
@@ -198,7 +198,12 @@ pub struct EditCollection {
|
||||
)]
|
||||
pub name: Option<String>,
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub description: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
pub description: Option<Option<String>>,
|
||||
pub status: Option<CollectionStatus>,
|
||||
#[validate(length(max = 64))]
|
||||
pub new_projects: Option<Vec<String>>,
|
||||
@@ -260,7 +265,7 @@ pub async fn collection_edit(
|
||||
SET description = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
description,
|
||||
description.as_ref(),
|
||||
id as database::models::ids::CollectionId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
@@ -328,11 +333,22 @@ pub async fn collection_edit(
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE collections
|
||||
SET updated = NOW()
|
||||
WHERE id = $1
|
||||
",
|
||||
collection_item.id as database::models::ids::CollectionId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
@@ -417,9 +433,8 @@ pub async fn collection_icon_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -481,9 +496,8 @@ pub async fn delete_collection_icon(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -519,9 +533,9 @@ pub async fn collection_delete(
|
||||
|
||||
let result =
|
||||
database::models::Collection::remove(collection.id, &mut transaction, &redis).await?;
|
||||
database::models::Collection::clear_cache(collection.id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Collection::clear_cache(collection.id, &redis).await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
|
||||
@@ -456,6 +456,7 @@ pub async fn organizations_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Organization::clear_cache(
|
||||
organization_item.id,
|
||||
Some(organization_item.name),
|
||||
@@ -463,7 +464,6 @@ pub async fn organizations_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
@@ -819,6 +819,7 @@ pub async fn organization_icon_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
database::models::Organization::clear_cache(
|
||||
organization_item.id,
|
||||
Some(organization_item.name),
|
||||
@@ -826,8 +827,6 @@ pub async fn organization_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInput(format!(
|
||||
@@ -904,6 +903,8 @@ pub async fn delete_organization_icon(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
database::models::Organization::clear_cache(
|
||||
organization_item.id,
|
||||
Some(organization_item.name),
|
||||
@@ -911,7 +912,5 @@ pub async fn delete_organization_icon(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
@@ -144,12 +144,6 @@ pub async fn paypal_webhook(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
crate::database::models::user_item::User::clear_caches(
|
||||
&[(crate::database::models::UserId(result.user_id), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE payouts
|
||||
@@ -168,6 +162,12 @@ pub async fn paypal_webhook(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
crate::database::models::user_item::User::clear_caches(
|
||||
&[(crate::database::models::UserId(result.user_id), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
"PAYMENT.PAYOUTS-ITEM.SUCCEEDED" => {
|
||||
@@ -265,12 +265,6 @@ pub async fn tremendous_webhook(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
crate::database::models::user_item::User::clear_caches(
|
||||
&[(crate::database::models::UserId(result.user_id), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE payouts
|
||||
@@ -289,6 +283,12 @@ pub async fn tremendous_webhook(
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
crate::database::models::user_item::User::clear_caches(
|
||||
&[(crate::database::models::UserId(result.user_id), None)],
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
"REWARDS.DELIVERY.SUCCEEDED" => {
|
||||
@@ -616,9 +616,9 @@ pub async fn create_payout(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
payout_item.insert(&mut transaction).await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
@@ -839,6 +839,8 @@ pub async fn project_edit(
|
||||
};
|
||||
|
||||
img::delete_unused_images(context, checkable_strings, &mut transaction, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
@@ -847,7 +849,6 @@ pub async fn project_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
@@ -1501,6 +1502,7 @@ pub async fn project_icon_edit(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(
|
||||
project_item.inner.id,
|
||||
project_item.inner.slug,
|
||||
@@ -1509,8 +1511,6 @@ pub async fn project_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInput(format!(
|
||||
@@ -1596,11 +1596,10 @@ pub async fn delete_project_icon(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
@@ -1736,6 +1735,7 @@ pub async fn add_gallery_item(
|
||||
}];
|
||||
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,
|
||||
@@ -1744,8 +1744,6 @@ pub async fn add_gallery_item(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInput(format!(
|
||||
@@ -1921,11 +1919,11 @@ pub async fn edit_gallery_item(
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
@@ -2027,11 +2025,11 @@ pub async fn delete_gallery_item(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse,
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct LinkPlatformQueryData {
|
||||
pub name: String,
|
||||
pub donation: bool,
|
||||
}
|
||||
|
||||
pub async fn link_platform_list(
|
||||
@@ -226,7 +227,10 @@ pub async fn link_platform_list(
|
||||
let results: Vec<LinkPlatformQueryData> = LinkPlatform::list(&**pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| LinkPlatformQueryData { name: x.name })
|
||||
.map(|x| LinkPlatformQueryData {
|
||||
name: x.name,
|
||||
donation: x.donation,
|
||||
})
|
||||
.collect();
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
@@ -348,10 +348,10 @@ pub async fn join_team(
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
User::clear_project_cache(&[current_user.id.into()], &redis).await?;
|
||||
TeamMember::clear_cache(team_id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"There is no pending request from this team".to_string(),
|
||||
@@ -542,9 +542,8 @@ pub async fn add_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
TeamMember::clear_cache(team_id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
TeamMember::clear_cache(team_id, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -691,9 +690,8 @@ pub async fn edit_team_member(
|
||||
)
|
||||
.await?;
|
||||
|
||||
TeamMember::clear_cache(id, &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
TeamMember::clear_cache(id, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -797,9 +795,8 @@ pub async fn transfer_ownership(
|
||||
)
|
||||
.await?;
|
||||
|
||||
TeamMember::clear_cache(id.into(), &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
TeamMember::clear_cache(id.into(), &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
@@ -925,10 +922,11 @@ pub async fn remove_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
TeamMember::clear_cache(id, &redis).await?;
|
||||
User::clear_project_cache(&[delete_member.user_id], &redis).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
|
||||
@@ -449,8 +449,8 @@ pub async fn user_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
||||
transaction.commit().await?;
|
||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
|
||||
@@ -664,6 +664,7 @@ pub async fn version_edit_helper(
|
||||
|
||||
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::Project::clear_cache(
|
||||
version_item.inner.project_id,
|
||||
@@ -672,7 +673,6 @@ pub async fn version_edit_helper(
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::CustomAuthentication(
|
||||
@@ -921,8 +921,8 @@ pub async fn version_schedule(
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
database::models::Version::clear_cache(&version_item, &redis).await?;
|
||||
transaction.commit().await?;
|
||||
database::models::Version::clear_cache(&version_item, &redis).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -1004,12 +1004,11 @@ pub async fn version_delete(
|
||||
|
||||
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?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
if result.is_some() {
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
|
||||
@@ -80,6 +80,8 @@ delegate_api_variant!(
|
||||
[add_gallery_item, ServiceResponse, id_or_slug: &str, image: ImageData, featured: bool, title: Option<String>, description: Option<String>, ordering: Option<i32>, pat: Option<&str>],
|
||||
[remove_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, pat: Option<&str>],
|
||||
[edit_gallery_item, ServiceResponse, id_or_slug: &str, image_url: &str, patch: HashMap<String, String>, pat: Option<&str>],
|
||||
[create_report, ServiceResponse, report_type: &str, id: &str, item_type: crate::common::api_common::models::CommonItemType, body: &str, pat: Option<&str>],
|
||||
[get_report, ServiceResponse, id: &str, pat: Option<&str>],
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use self::models::{
|
||||
CommonCategoryData, CommonLoaderData, CommonNotification, CommonProject, CommonTeamMember,
|
||||
CommonVersion,
|
||||
CommonCategoryData, CommonItemType, CommonLoaderData, CommonNotification, CommonProject,
|
||||
CommonTeamMember, CommonVersion,
|
||||
};
|
||||
use self::request_data::{ImageData, ProjectCreationRequestData};
|
||||
use actix_web::dev::ServiceResponse;
|
||||
@@ -118,6 +118,15 @@ pub trait ApiProject {
|
||||
patch: HashMap<String, String>,
|
||||
pat: Option<&str>,
|
||||
) -> ServiceResponse;
|
||||
async fn create_report(
|
||||
&self,
|
||||
report_type: &str,
|
||||
id: &str,
|
||||
item_type: CommonItemType,
|
||||
body: &str,
|
||||
pat: Option<&str>,
|
||||
) -> ServiceResponse;
|
||||
async fn get_report(&self, id: &str, pat: Option<&str>) -> ServiceResponse;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
@@ -11,7 +11,7 @@ use labrinth::models::{
|
||||
users::{User, UserId},
|
||||
};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Fields shared by every version of the API.
|
||||
// No struct in here should have ANY field that
|
||||
@@ -119,3 +119,23 @@ pub struct CommonNotification {
|
||||
pub struct CommonNotificationAction {
|
||||
pub action_route: (String, String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum CommonItemType {
|
||||
Project,
|
||||
Version,
|
||||
User,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl CommonItemType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
CommonItemType::Project => "project",
|
||||
CommonItemType::Version => "version",
|
||||
CommonItemType::User => "user",
|
||||
CommonItemType::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::common::{
|
||||
api_common::{
|
||||
models::{CommonProject, CommonVersion},
|
||||
models::{CommonItemType, CommonProject, CommonVersion},
|
||||
request_data::{ImageData, ProjectCreationRequestData},
|
||||
Api, ApiProject, AppendsOptionalPat,
|
||||
},
|
||||
@@ -266,6 +266,39 @@ impl ApiProject for ApiV2 {
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_report(
|
||||
&self,
|
||||
report_type: &str,
|
||||
id: &str,
|
||||
item_type: CommonItemType,
|
||||
body: &str,
|
||||
pat: Option<&str>,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/v3/report")
|
||||
.append_pat(pat)
|
||||
.set_json(json!(
|
||||
{
|
||||
"report_type": report_type,
|
||||
"item_id": id,
|
||||
"item_type": item_type.as_str(),
|
||||
"body": body,
|
||||
}
|
||||
))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
async fn get_report(&self, id: &str, pat: Option<&str>) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v3/report/{id}", id = id))
|
||||
.append_pat(pat)
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
async fn schedule_project(
|
||||
&self,
|
||||
id_or_slug: &str,
|
||||
|
||||
@@ -3,7 +3,9 @@ use actix_web::{
|
||||
test::{self, TestRequest},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use labrinth::routes::v2::tags::{CategoryData, GameVersionQueryData, LoaderData};
|
||||
use labrinth::routes::v2::tags::{
|
||||
CategoryData, DonationPlatformQueryData, GameVersionQueryData, LoaderData,
|
||||
};
|
||||
|
||||
use crate::common::{
|
||||
api_common::{
|
||||
@@ -57,6 +59,21 @@ impl ApiV2 {
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
|
||||
pub async fn get_donation_platforms(&self) -> ServiceResponse {
|
||||
let req = TestRequest::get()
|
||||
.uri("/v2/tag/donation_platform")
|
||||
.append_pat(ADMIN_USER_PAT)
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
pub async fn get_donation_platforms_deserialized(&self) -> Vec<DonationPlatformQueryData> {
|
||||
let resp = self.get_donation_platforms().await;
|
||||
println!("Response: {:?}", resp.response().body());
|
||||
assert_eq!(resp.status(), 200);
|
||||
test::read_body_json(resp).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
@@ -17,7 +17,7 @@ use serde_json::json;
|
||||
|
||||
use crate::common::{
|
||||
api_common::{
|
||||
models::{CommonProject, CommonVersion},
|
||||
models::{CommonItemType, CommonProject, CommonVersion},
|
||||
request_data::{ImageData, ProjectCreationRequestData},
|
||||
Api, ApiProject, AppendsOptionalPat,
|
||||
},
|
||||
@@ -219,6 +219,39 @@ impl ApiProject for ApiV3 {
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_report(
|
||||
&self,
|
||||
report_type: &str,
|
||||
id: &str,
|
||||
item_type: CommonItemType,
|
||||
body: &str,
|
||||
pat: Option<&str>,
|
||||
) -> ServiceResponse {
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/v3/report")
|
||||
.append_pat(pat)
|
||||
.set_json(json!(
|
||||
{
|
||||
"report_type": report_type,
|
||||
"item_id": id,
|
||||
"item_type": item_type.as_str(),
|
||||
"body": body,
|
||||
}
|
||||
))
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
async fn get_report(&self, id: &str, pat: Option<&str>) -> ServiceResponse {
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/v3/report/{id}", id = id))
|
||||
.append_pat(pat)
|
||||
.to_request();
|
||||
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
async fn schedule_project(
|
||||
&self,
|
||||
id_or_slug: &str,
|
||||
|
||||
@@ -18,6 +18,7 @@ use labrinth::models::teams::ProjectPermissions;
|
||||
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::api_common::models::CommonItemType;
|
||||
use crate::common::api_common::request_data::ProjectCreationRequestData;
|
||||
use crate::common::api_common::{ApiProject, ApiTeams, ApiVersion, AppendsOptionalPat};
|
||||
use crate::common::dummy_data::{DummyImage, TestFile};
|
||||
@@ -590,6 +591,94 @@ pub async fn test_bulk_edit_links() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn delete_project_with_report() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let api = &test_env.api;
|
||||
let alpha_project_id: &str = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let beta_project_id: &str = &test_env.dummy.as_ref().unwrap().project_beta.project_id;
|
||||
|
||||
// Create a report for the project
|
||||
let resp = api
|
||||
.create_report(
|
||||
"copyright",
|
||||
alpha_project_id,
|
||||
CommonItemType::Project,
|
||||
"Hey! This is my project, copied without permission!",
|
||||
ENEMY_USER_PAT, // Enemy makes a report
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let value = test::read_body_json::<serde_json::Value, _>(resp).await;
|
||||
let alpha_report_id = value["id"].as_str().unwrap();
|
||||
|
||||
// Confirm existence
|
||||
let resp = api
|
||||
.get_report(
|
||||
alpha_report_id,
|
||||
ENEMY_USER_PAT, // Enemy makes a report
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
// Do the same for beta
|
||||
let resp = api
|
||||
.create_report(
|
||||
"copyright",
|
||||
beta_project_id,
|
||||
CommonItemType::Project,
|
||||
"Hey! This is my project, copied without permission!",
|
||||
ENEMY_USER_PAT, // Enemy makes a report
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let value = test::read_body_json::<serde_json::Value, _>(resp).await;
|
||||
let beta_report_id = value["id"].as_str().unwrap();
|
||||
|
||||
// Delete the project
|
||||
let resp = api.remove_project(alpha_project_id, USER_USER_PAT).await;
|
||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
// Confirm that the project is gone from the cache
|
||||
let mut redis_pool = test_env.db.redis_pool.connect().await.unwrap();
|
||||
assert_eq!(
|
||||
redis_pool
|
||||
.get(PROJECTS_SLUGS_NAMESPACE, "demo")
|
||||
.await
|
||||
.unwrap()
|
||||
.and_then(|x| x.parse::<i64>().ok()),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
redis_pool
|
||||
.get(PROJECTS_SLUGS_NAMESPACE, alpha_project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.and_then(|x| x.parse::<i64>().ok()),
|
||||
None
|
||||
);
|
||||
|
||||
// Report for alpha no longer exists
|
||||
let resp = api
|
||||
.get_report(
|
||||
alpha_report_id,
|
||||
ENEMY_USER_PAT, // Enemy makes a report
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
// Confirm that report for beta still exists
|
||||
let resp = api
|
||||
.get_report(
|
||||
beta_report_id,
|
||||
ENEMY_USER_PAT, // Enemy makes a report
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_patch_project_v3() {
|
||||
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV3>| async move {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use itertools::Itertools;
|
||||
use labrinth::routes::v2::tags::DonationPlatformQueryData;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -62,3 +63,45 @@ async fn get_tags() {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn get_donation_platforms() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
|
||||
let api = &test_env.api;
|
||||
let mut donation_platforms_unsorted = api.get_donation_platforms_deserialized().await;
|
||||
|
||||
// These tests match dummy data and will need to be updated if the dummy data changes
|
||||
let mut included = vec![
|
||||
DonationPlatformQueryData {
|
||||
short: "patreon".to_string(),
|
||||
name: "Patreon".to_string(),
|
||||
},
|
||||
DonationPlatformQueryData {
|
||||
short: "ko-fi".to_string(),
|
||||
name: "Ko-fi".to_string(),
|
||||
},
|
||||
DonationPlatformQueryData {
|
||||
short: "paypal".to_string(),
|
||||
name: "PayPal".to_string(),
|
||||
},
|
||||
DonationPlatformQueryData {
|
||||
short: "bmac".to_string(),
|
||||
name: "Buy Me A Coffee".to_string(),
|
||||
},
|
||||
DonationPlatformQueryData {
|
||||
short: "github".to_string(),
|
||||
name: "GitHub Sponsors".to_string(),
|
||||
},
|
||||
DonationPlatformQueryData {
|
||||
short: "other".to_string(),
|
||||
name: "Other".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
included.sort_by(|a, b| a.short.cmp(&b.short));
|
||||
donation_platforms_unsorted.sort_by(|a, b| a.short.cmp(&b.short));
|
||||
|
||||
assert_eq!(donation_platforms_unsorted, included);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user