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": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
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 collection_id: CollectionId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
pub status: CollectionStatus,
|
pub status: CollectionStatus,
|
||||||
pub projects: Vec<ProjectId>,
|
pub projects: Vec<ProjectId>,
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ pub struct Collection {
|
|||||||
pub id: CollectionId,
|
pub id: CollectionId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub updated: DateTime<Utc>,
|
pub updated: DateTime<Utc>,
|
||||||
pub icon_url: Option<String>,
|
pub icon_url: Option<String>,
|
||||||
@@ -73,7 +73,7 @@ impl Collection {
|
|||||||
self.id as CollectionId,
|
self.id as CollectionId,
|
||||||
self.user_id as UserId,
|
self.user_id as UserId,
|
||||||
&self.name,
|
&self.name,
|
||||||
&self.description,
|
self.description.as_ref(),
|
||||||
self.created,
|
self.created,
|
||||||
self.icon_url.as_ref(),
|
self.icon_url.as_ref(),
|
||||||
self.status.to_string(),
|
self.status.to_string(),
|
||||||
|
|||||||
@@ -344,6 +344,28 @@ impl Project {
|
|||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.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!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
DELETE FROM reports
|
DELETE FROM reports
|
||||||
@@ -398,8 +420,6 @@ impl Project {
|
|||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
models::Thread::remove_full(project.thread_id, transaction).await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
DELETE FROM mods
|
DELETE FROM mods
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::models::projects::{
|
|||||||
ProjectStatus, Version, VersionFile, VersionStatus, VersionType,
|
ProjectStatus, Version, VersionFile, VersionStatus, VersionType,
|
||||||
};
|
};
|
||||||
use crate::models::threads::ThreadId;
|
use crate::models::threads::ThreadId;
|
||||||
use crate::routes::v2_reroute;
|
use crate::routes::v2_reroute::{self, capitalize_first};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
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.
|
/// The title or name of the collection.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// A short description of the collection.
|
/// A short description of the collection.
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
|
|
||||||
/// An icon URL for the collection.
|
/// An icon URL for the collection.
|
||||||
pub icon_url: Option<String>,
|
pub icon_url: Option<String>,
|
||||||
|
|||||||
@@ -720,6 +720,8 @@ pub async fn process_payout(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
if !clear_cache_users.is_empty() {
|
if !clear_cache_users.is_empty() {
|
||||||
crate::database::models::User::clear_caches(
|
crate::database::models::User::clear_caches(
|
||||||
&clear_cache_users
|
&clear_cache_users
|
||||||
@@ -731,8 +733,6 @@ pub async fn process_payout(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,11 +131,10 @@ impl AuthQueue {
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
PersonalAccessToken::clear_cache(clear_cache_pats, redis).await?;
|
|
||||||
|
|
||||||
update_oauth_access_token_last_used(oauth_access_token_queue, &mut transaction).await?;
|
update_oauth_access_token_last_used(oauth_access_token_queue, &mut transaction).await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
PersonalAccessToken::clear_cache(clear_cache_pats, redis).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1195,8 +1195,8 @@ pub async fn auth_callback(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::database::models::User::clear_caches(&[(id, None)], &redis).await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(id, None)], &redis).await?;
|
||||||
|
|
||||||
if let Some(url) = url {
|
if let Some(url) = url {
|
||||||
Ok(HttpResponse::TemporaryRedirect()
|
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?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
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?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
"backup_codes": codes,
|
"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?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
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?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
@@ -2210,8 +2210,8 @@ pub async fn set_email(
|
|||||||
"We need to verify your email address.",
|
"We need to verify your email address.",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
@@ -2300,8 +2300,8 @@ pub async fn verify_email(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Flow::remove(&email.flow, &redis).await?;
|
Flow::remove(&email.flow, &redis).await?;
|
||||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -127,12 +127,12 @@ pub async fn create_pat(
|
|||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
vec![(None, None, Some(user.id.into()))],
|
vec![(None, None, Some(user.id.into()))],
|
||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
Ok(HttpResponse::Ok().json(PersonalAccessToken {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
@@ -232,12 +232,12 @@ pub async fn edit_pat(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
transaction.commit().await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,12 +269,12 @@ pub async fn delete_pat(
|
|||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
database::models::pat_item::PersonalAccessToken::remove(pat.id, &mut transaction)
|
database::models::pat_item::PersonalAccessToken::remove(pat.id, &mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
transaction.commit().await?;
|
||||||
database::models::pat_item::PersonalAccessToken::clear_cache(
|
database::models::pat_item::PersonalAccessToken::clear_cache(
|
||||||
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
vec![(Some(pat.id), Some(pat.access_token), Some(pat.user_id))],
|
||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
transaction.commit().await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ pub async fn delete(
|
|||||||
if session.user_id == current_user.id.into() {
|
if session.user_id == current_user.id.into() {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
DBSession::remove(session.id, &mut transaction).await?;
|
DBSession::remove(session.id, &mut transaction).await?;
|
||||||
|
transaction.commit().await?;
|
||||||
DBSession::clear_cache(
|
DBSession::clear_cache(
|
||||||
vec![(
|
vec![(
|
||||||
Some(session.id),
|
Some(session.id),
|
||||||
@@ -196,7 +197,6 @@ pub async fn delete(
|
|||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
transaction.commit().await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +232,7 @@ pub async fn refresh(
|
|||||||
|
|
||||||
DBSession::remove(session.id, &mut transaction).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(
|
DBSession::clear_cache(
|
||||||
vec![(
|
vec![(
|
||||||
Some(session.id),
|
Some(session.id),
|
||||||
@@ -242,8 +243,6 @@ pub async fn refresh(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(Session::from(new_session, true, None)))
|
Ok(HttpResponse::Ok().json(Session::from(new_session, true, None)))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::Authentication(
|
Err(ApiError::Authentication(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use super::ApiError;
|
|||||||
use crate::database::models::loader_fields::LoaderFieldEnumValue;
|
use crate::database::models::loader_fields::LoaderFieldEnumValue;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::v2::projects::LegacySideType;
|
use crate::models::v2::projects::LegacySideType;
|
||||||
|
use crate::routes::v2_reroute::capitalize_first;
|
||||||
use crate::routes::v3::tags::{
|
use crate::routes::v3::tags::{
|
||||||
LinkPlatformQueryData, LoaderData as LoaderDataV3, LoaderFieldsEnumQuery,
|
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)
|
.or_else(v2_reroute::flatten_404_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
|
||||||
pub struct DonationPlatformQueryData {
|
pub struct DonationPlatformQueryData {
|
||||||
// The difference between name and short is removed in v3.
|
// 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)
|
// 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 short: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +195,26 @@ pub async fn donation_platform_list(
|
|||||||
Ok(platforms) => {
|
Ok(platforms) => {
|
||||||
let platforms = platforms
|
let platforms = platforms
|
||||||
.into_iter()
|
.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<_>>();
|
.collect::<Vec<_>>();
|
||||||
HttpResponse::Ok().json(platforms)
|
HttpResponse::Ok().json(platforms)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,34 +100,66 @@ pub async fn version_create(
|
|||||||
json!(legacy_create.game_versions),
|
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.
|
// Copies side types of another version of the project.
|
||||||
// If no version exists, defaults to all false.
|
// 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,
|
// 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 = [
|
let side_type_loader_field_names = [
|
||||||
"singleplayer",
|
"singleplayer",
|
||||||
"client_and_server",
|
"client_and_server",
|
||||||
"client_only",
|
"client_only",
|
||||||
"server_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
|
// Handle project type via file extension prediction
|
||||||
let mut project_type = None;
|
let mut project_type = None;
|
||||||
for file_part in &legacy_create.file_parts {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub struct CollectionCreateData {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[validate(length(min = 3, max = 255))]
|
#[validate(length(min = 3, max = 255))]
|
||||||
/// A short description of the collection.
|
/// A short description of the collection.
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
#[validate(length(max = 32))]
|
#[validate(length(max = 32))]
|
||||||
#[serde(default = "Vec::new")]
|
#[serde(default = "Vec::new")]
|
||||||
/// A list of initial projects to use with the created collection
|
/// A list of initial projects to use with the created collection
|
||||||
@@ -198,7 +198,12 @@ pub struct EditCollection {
|
|||||||
)]
|
)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[validate(length(min = 3, max = 256))]
|
#[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>,
|
pub status: Option<CollectionStatus>,
|
||||||
#[validate(length(max = 64))]
|
#[validate(length(max = 64))]
|
||||||
pub new_projects: Option<Vec<String>>,
|
pub new_projects: Option<Vec<String>>,
|
||||||
@@ -260,7 +265,7 @@ pub async fn collection_edit(
|
|||||||
SET description = $1
|
SET description = $1
|
||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
description,
|
description.as_ref(),
|
||||||
id as database::models::ids::CollectionId,
|
id as database::models::ids::CollectionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@@ -328,11 +333,22 @@ pub async fn collection_edit(
|
|||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.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?;
|
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
@@ -417,9 +433,8 @@ pub async fn collection_icon_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
@@ -481,9 +496,8 @@ pub async fn delete_collection_icon(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
database::models::Collection::clear_cache(collection_item.id, &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
@@ -519,9 +533,9 @@ pub async fn collection_delete(
|
|||||||
|
|
||||||
let result =
|
let result =
|
||||||
database::models::Collection::remove(collection.id, &mut transaction, &redis).await?;
|
database::models::Collection::remove(collection.id, &mut transaction, &redis).await?;
|
||||||
database::models::Collection::clear_cache(collection.id, &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
database::models::Collection::clear_cache(collection.id, &redis).await?;
|
||||||
|
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
|||||||
@@ -456,6 +456,7 @@ pub async fn organizations_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
database::models::Organization::clear_cache(
|
database::models::Organization::clear_cache(
|
||||||
organization_item.id,
|
organization_item.id,
|
||||||
Some(organization_item.name),
|
Some(organization_item.name),
|
||||||
@@ -463,7 +464,6 @@ pub async fn organizations_edit(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthentication(
|
Err(ApiError::CustomAuthentication(
|
||||||
@@ -819,6 +819,7 @@ pub async fn organization_icon_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
database::models::Organization::clear_cache(
|
database::models::Organization::clear_cache(
|
||||||
organization_item.id,
|
organization_item.id,
|
||||||
Some(organization_item.name),
|
Some(organization_item.name),
|
||||||
@@ -826,8 +827,6 @@ pub async fn organization_icon_edit(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::InvalidInput(format!(
|
Err(ApiError::InvalidInput(format!(
|
||||||
@@ -904,6 +903,8 @@ pub async fn delete_organization_icon(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
database::models::Organization::clear_cache(
|
database::models::Organization::clear_cache(
|
||||||
organization_item.id,
|
organization_item.id,
|
||||||
Some(organization_item.name),
|
Some(organization_item.name),
|
||||||
@@ -911,7 +912,5 @@ pub async fn delete_organization_icon(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,12 +144,6 @@ pub async fn paypal_webhook(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::database::models::user_item::User::clear_caches(
|
|
||||||
&[(crate::database::models::UserId(result.user_id), None)],
|
|
||||||
&redis,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE payouts
|
UPDATE payouts
|
||||||
@@ -168,6 +162,12 @@ pub async fn paypal_webhook(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().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" => {
|
"PAYMENT.PAYOUTS-ITEM.SUCCEEDED" => {
|
||||||
@@ -265,12 +265,6 @@ pub async fn tremendous_webhook(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
crate::database::models::user_item::User::clear_caches(
|
|
||||||
&[(crate::database::models::UserId(result.user_id), None)],
|
|
||||||
&redis,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE payouts
|
UPDATE payouts
|
||||||
@@ -289,6 +283,12 @@ pub async fn tremendous_webhook(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().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" => {
|
"REWARDS.DELIVERY.SUCCEEDED" => {
|
||||||
@@ -616,9 +616,9 @@ pub async fn create_payout(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
payout_item.insert(&mut transaction).await?;
|
payout_item.insert(&mut transaction).await?;
|
||||||
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
crate::database::models::User::clear_caches(&[(user.id, None)], &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -839,6 +839,8 @@ pub async fn project_edit(
|
|||||||
};
|
};
|
||||||
|
|
||||||
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(
|
db_models::Project::clear_cache(
|
||||||
project_item.inner.id,
|
project_item.inner.id,
|
||||||
project_item.inner.slug,
|
project_item.inner.slug,
|
||||||
@@ -847,7 +849,6 @@ pub async fn project_edit(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthentication(
|
Err(ApiError::CustomAuthentication(
|
||||||
@@ -1501,6 +1502,7 @@ pub async fn project_icon_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
db_models::Project::clear_cache(
|
db_models::Project::clear_cache(
|
||||||
project_item.inner.id,
|
project_item.inner.id,
|
||||||
project_item.inner.slug,
|
project_item.inner.slug,
|
||||||
@@ -1509,8 +1511,6 @@ pub async fn project_icon_edit(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::InvalidInput(format!(
|
Err(ApiError::InvalidInput(format!(
|
||||||
@@ -1596,11 +1596,10 @@ pub async fn delete_project_icon(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
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?;
|
GalleryItem::insert_many(gallery_item, project_item.inner.id, &mut transaction).await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
db_models::Project::clear_cache(
|
db_models::Project::clear_cache(
|
||||||
project_item.inner.id,
|
project_item.inner.id,
|
||||||
project_item.inner.slug,
|
project_item.inner.slug,
|
||||||
@@ -1744,8 +1744,6 @@ pub async fn add_gallery_item(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::InvalidInput(format!(
|
Err(ApiError::InvalidInput(format!(
|
||||||
@@ -1921,11 +1919,11 @@ pub async fn edit_gallery_item(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2027,11 +2025,11 @@ pub async fn delete_gallery_item(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse,
|
|||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LinkPlatformQueryData {
|
pub struct LinkPlatformQueryData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub donation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn link_platform_list(
|
pub async fn link_platform_list(
|
||||||
@@ -226,7 +227,10 @@ pub async fn link_platform_list(
|
|||||||
let results: Vec<LinkPlatformQueryData> = LinkPlatform::list(&**pool, &redis)
|
let results: Vec<LinkPlatformQueryData> = LinkPlatform::list(&**pool, &redis)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| LinkPlatformQueryData { name: x.name })
|
.map(|x| LinkPlatformQueryData {
|
||||||
|
name: x.name,
|
||||||
|
donation: x.donation,
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,10 +348,10 @@ pub async fn join_team(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
User::clear_project_cache(&[current_user.id.into()], &redis).await?;
|
User::clear_project_cache(&[current_user.id.into()], &redis).await?;
|
||||||
TeamMember::clear_cache(team_id, &redis).await?;
|
TeamMember::clear_cache(team_id, &redis).await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"There is no pending request from this team".to_string(),
|
"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?;
|
transaction.commit().await?;
|
||||||
|
TeamMember::clear_cache(team_id, &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
@@ -691,9 +690,8 @@ pub async fn edit_team_member(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
TeamMember::clear_cache(id, &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
TeamMember::clear_cache(id, &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
@@ -797,9 +795,8 @@ pub async fn transfer_ownership(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
TeamMember::clear_cache(id.into(), &redis).await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
TeamMember::clear_cache(id.into(), &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
@@ -925,10 +922,11 @@ pub async fn remove_team_member(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
TeamMember::clear_cache(id, &redis).await?;
|
TeamMember::clear_cache(id, &redis).await?;
|
||||||
User::clear_project_cache(&[delete_member.user_id], &redis).await?;
|
User::clear_project_cache(&[delete_member.user_id], &redis).await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
|
|||||||
@@ -449,8 +449,8 @@ pub async fn user_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthentication(
|
Err(ApiError::CustomAuthentication(
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ pub async fn version_edit_helper(
|
|||||||
|
|
||||||
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(
|
database::models::Project::clear_cache(
|
||||||
version_item.inner.project_id,
|
version_item.inner.project_id,
|
||||||
@@ -672,7 +673,6 @@ pub async fn version_edit_helper(
|
|||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
transaction.commit().await?;
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthentication(
|
Err(ApiError::CustomAuthentication(
|
||||||
@@ -921,8 +921,8 @@ pub async fn version_schedule(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
database::models::Version::clear_cache(&version_item, &redis).await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
database::models::Version::clear_cache(&version_item, &redis).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
@@ -1004,12 +1004,11 @@ pub async fn version_delete(
|
|||||||
|
|
||||||
let result =
|
let result =
|
||||||
database::models::Version::remove_full(version.inner.id, &redis, &mut transaction).await?;
|
database::models::Version::remove_full(version.inner.id, &redis, &mut transaction).await?;
|
||||||
|
transaction.commit().await?;
|
||||||
remove_documents(&[version.inner.id.into()], &search_config).await?;
|
remove_documents(&[version.inner.id.into()], &search_config).await?;
|
||||||
database::models::Project::clear_cache(version.inner.project_id, None, Some(true), &redis)
|
database::models::Project::clear_cache(version.inner.project_id, None, Some(true), &redis)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} 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>],
|
[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>],
|
[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>],
|
[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 std::collections::HashMap;
|
||||||
|
|
||||||
use self::models::{
|
use self::models::{
|
||||||
CommonCategoryData, CommonLoaderData, CommonNotification, CommonProject, CommonTeamMember,
|
CommonCategoryData, CommonItemType, CommonLoaderData, CommonNotification, CommonProject,
|
||||||
CommonVersion,
|
CommonTeamMember, CommonVersion,
|
||||||
};
|
};
|
||||||
use self::request_data::{ImageData, ProjectCreationRequestData};
|
use self::request_data::{ImageData, ProjectCreationRequestData};
|
||||||
use actix_web::dev::ServiceResponse;
|
use actix_web::dev::ServiceResponse;
|
||||||
@@ -118,6 +118,15 @@ pub trait ApiProject {
|
|||||||
patch: HashMap<String, String>,
|
patch: HashMap<String, String>,
|
||||||
pat: Option<&str>,
|
pat: Option<&str>,
|
||||||
) -> ServiceResponse;
|
) -> 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)]
|
#[async_trait(?Send)]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use labrinth::models::{
|
|||||||
users::{User, UserId},
|
users::{User, UserId},
|
||||||
};
|
};
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Fields shared by every version of the API.
|
// Fields shared by every version of the API.
|
||||||
// No struct in here should have ANY field that
|
// No struct in here should have ANY field that
|
||||||
@@ -119,3 +119,23 @@ pub struct CommonNotification {
|
|||||||
pub struct CommonNotificationAction {
|
pub struct CommonNotificationAction {
|
||||||
pub action_route: (String, String),
|
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::{
|
use crate::common::{
|
||||||
api_common::{
|
api_common::{
|
||||||
models::{CommonProject, CommonVersion},
|
models::{CommonItemType, CommonProject, CommonVersion},
|
||||||
request_data::{ImageData, ProjectCreationRequestData},
|
request_data::{ImageData, ProjectCreationRequestData},
|
||||||
Api, ApiProject, AppendsOptionalPat,
|
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(
|
async fn schedule_project(
|
||||||
&self,
|
&self,
|
||||||
id_or_slug: &str,
|
id_or_slug: &str,
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use actix_web::{
|
|||||||
test::{self, TestRequest},
|
test::{self, TestRequest},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
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::{
|
use crate::common::{
|
||||||
api_common::{
|
api_common::{
|
||||||
@@ -57,6 +59,21 @@ impl ApiV2 {
|
|||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
test::read_body_json(resp).await
|
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)]
|
#[async_trait(?Send)]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use serde_json::json;
|
|||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
api_common::{
|
api_common::{
|
||||||
models::{CommonProject, CommonVersion},
|
models::{CommonItemType, CommonProject, CommonVersion},
|
||||||
request_data::{ImageData, ProjectCreationRequestData},
|
request_data::{ImageData, ProjectCreationRequestData},
|
||||||
Api, ApiProject, AppendsOptionalPat,
|
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(
|
async fn schedule_project(
|
||||||
&self,
|
&self,
|
||||||
id_or_slug: &str,
|
id_or_slug: &str,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use labrinth::models::teams::ProjectPermissions;
|
|||||||
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
|
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::common::api_common::models::CommonItemType;
|
||||||
use crate::common::api_common::request_data::ProjectCreationRequestData;
|
use crate::common::api_common::request_data::ProjectCreationRequestData;
|
||||||
use crate::common::api_common::{ApiProject, ApiTeams, ApiVersion, AppendsOptionalPat};
|
use crate::common::api_common::{ApiProject, ApiTeams, ApiVersion, AppendsOptionalPat};
|
||||||
use crate::common::dummy_data::{DummyImage, TestFile};
|
use crate::common::dummy_data::{DummyImage, TestFile};
|
||||||
@@ -590,6 +591,94 @@ pub async fn test_bulk_edit_links() {
|
|||||||
.await;
|
.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]
|
#[actix_rt::test]
|
||||||
async fn permissions_patch_project_v3() {
|
async fn permissions_patch_project_v3() {
|
||||||
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV3>| async move {
|
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV3>| async move {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use labrinth::routes::v2::tags::DonationPlatformQueryData;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
@@ -62,3 +63,45 @@ async fn get_tags() {
|
|||||||
})
|
})
|
||||||
.await;
|
.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