fix(labrinth): ensure versions get removed from search indexes before ending route execution (#3789)

* fix(labrinth): ensure versions get removed from search indexes before ending route execution

* chore: run `sqlx prepare`

* chore(labrinth): simplify `remove_documents` a little

* chore: tweak new comment
This commit is contained in:
Alejandro González
2025-06-16 17:48:04 +02:00
committed by GitHub
parent 5bdff3929b
commit 97e4d8e132
41 changed files with 888 additions and 971 deletions

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW()\n WHERE (id = $1)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "010cafcafb6adc25b00e3c81d844736b0245e752a90334c58209d8a02536c800"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET webhook_sent = TRUE\n WHERE id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "124fbf0544ea6989d6dc5e840405dbc76d7385276a38ad79d9093c53c73bbde2"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM mods_links\n WHERE joining_mod_id = $1 AND joining_platform_id IN (\n SELECT id FROM link_platforms WHERE name = ANY($2)\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"TextArray"
]
},
"nullable": []
},
"hash": "186d0e933ece20163915926293a01754ff571de4f06e521bb4f7c0207268e03b"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET license = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "19dc22c4d6d14222f8e8bace74c2961761c53b7375460ade15af921754d5d7da"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = FALSE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "299b8ea6e7a0048fa389cc4432715dc2a09e227d2f08e91167a43372a7ac6e35"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = TRUE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "3fcfed18cbfb37866e0fa57a4e95efb326864f8219941d1b696add39ed333ad1"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = TRUE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET description = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "4e9f9eafbfd705dfc94571018cb747245a98ea61bad3fae4b3ce284229d99955"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET slug = LOWER($1)\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": []
},
"hash": "4fb5bd341369b4beb6b4a88de296b608ea5441a96db9f7360fbdccceb4628202"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET summary = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "595f4e7432d5b41002988c6cc6b0b1f09273ad02c319e6631c74d80a9b278328"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int4",
"Varchar"
]
},
"nullable": []
},
"hash": "6366891bb34a14278f1ae857b8d6f68dff44badae9ae5c5aceba3c32e8d00356"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET requested_status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "66b06ddcd0a4cf01e716331befa393a12631fe6752a7d078bda06b24d50daae2"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET name = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "70be97b02e402de0490ade5866c47232f9c341add2f3838cc3ae1a07a310d561"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message_body = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "79040825457845cc078be7b3293804d6fb2e05ffce07e7b4248d8705d6fc6e61"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET monetization_status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "7916fe4f04067324ae05598ec9dc6f97f18baf9eda30c64f32677158ada87478"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET slug = LOWER($1)\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": []
},
"hash": "7e403d399ddd3279c4c65db7b9ea850cdd9fef3df1b3f7d5f62e079b4522f2ca"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET approved = NOW()\n WHERE id = $1 AND approved IS NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "92d805d2e13cfc0f2220f15b0a35ff71e654e5e6b386766e6c6047cf3861b26e"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET license_url = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "9482a3419337911ac6a10eeaf065e29589ee1b707729344e81d183c713aa0d28"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = FALSE\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "a0148ff25855202e7bb220b6a2bc9220a95e309fb0dae41d9a05afa86e6b33af"
}

View File

@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET approved = NOW()\n WHERE id = $1 AND approved IS NULL\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "a11d613479d09dff5fcdc45ab7a0341fb1b4738f0ede71572d939ef0984bd65f"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET license = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "a5ae1fe0ca4ca8432736398fed25687173b2fbde3405340a5579c5ef68cb5218"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "a74230ad1bb1b13bab850e204436e7746a96f9605afe2ca62d6d8337530cb5ad"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "abf790170e3a807ffe8b3a188da620c89e6398f38ff066220fdadffe8e7481c1"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET summary = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "b677e66031752e66d2219079a559e368c6cea1800da8a5f9d50ba5b1ac3a15fc"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET license_url = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "c100a3be0e1b7bf449576c4052d87494979cb89d194805a5ce9e928eef796ae9"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW()\n WHERE (id = $1)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
},
"hash": "c6693ea80ab1675dd2da72d70add734a92bb25f17a0536968e4b9a4dbe05cf5b"
}

View File

@@ -1,15 +1,14 @@
{ {
"db_name": "PostgreSQL", "db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET name = $1\n WHERE (id = $2)\n ", "query": "\n UPDATE mods\n SET webhook_sent = TRUE\n WHERE id = $1\n ",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
"Left": [ "Left": [
"Varchar",
"Int8" "Int8"
] ]
}, },
"nullable": [] "nullable": []
}, },
"hash": "9c1b6ba7cbe2619ff767ee7bbfb01725dc3324d284b2f20cf393574ab3bc655f" "hash": "cec98010827455127da68a2bc5cd3c1ee3bfd357a6a8604febad3ed214a9b77b"
} }

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "d010207297e1c4f2ebfb0a81caf45481c94edb1e8d8ac47db13ec0ff9b2f5328"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "d331ca8f22da418cf654985c822ce4466824beaa00dea64cde90dc651a03024b"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT tm.user_id id\n FROM team_members tm\n WHERE tm.team_id = $1 AND tm.accepted\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false
]
},
"hash": "d5ad5a67fe53351b760335b80501f09a2799bf575af90beeac94193fe8c4388b"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET requested_status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "e42e63db3ae4d1d745508b80651494da8738873b98aa608792af19e60b9fb998"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET description = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "e7654740161726b2aef4f7c9a26eb00efcac9f6285a39d8df06d606613684ba3"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "e925b15ec46f0263c7775ba1ba00ed11cfd6749fa792d4eabed73b619f230585"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT tm.user_id id\n FROM team_members tm\n WHERE tm.team_id = $1 AND tm.accepted\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
false
]
},
"hash": "ea1525cbe7460d0d9e9da8f448c661f7209bc1a7a04e2ea0026fa69c3f550a14"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET moderation_message_body = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "ed1d5d9433bc7f4a360431ecfdd9430c5e58cd6d1c623c187d8661200400b1a4"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "f34bbe639ad21801258dc8beaab9877229a451761be07f85a1dd04d027832329"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE mods\n SET monetization_status = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "fa874e2c55995feaa5e0d3cd54db82b88af15477d616d0d3b3c6967b31d967f7"
}

View File

@@ -274,9 +274,13 @@ pub async fn project_edit(
ApiError::Validation(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let string = info.into_inner().0; let Some(project_item) =
let result = db_models::DBProject::get(&string, &**pool, &redis).await?; db_models::DBProject::get(&info.into_inner().0, &**pool, &redis)
if let Some(project_item) = result { .await?
else {
return Err(ApiError::NotFound);
};
let id = project_item.inner.id; let id = project_item.inner.id;
let (team_member, organization_team_member) = let (team_member, organization_team_member) =
@@ -287,13 +291,16 @@ pub async fn project_edit(
) )
.await?; .await?;
let permissions = ProjectPermissions::get_permissions_by_role( let Some(perms) = ProjectPermissions::get_permissions_by_role(
&user.role, &user.role,
&team_member, &team_member,
&organization_team_member, &organization_team_member,
); ) else {
return Err(ApiError::CustomAuthentication(
"You do not have permission to edit this project!".to_string(),
));
};
if let Some(perms) = permissions {
let mut transaction = pool.begin().await?; let mut transaction = pool.begin().await?;
if let Some(name) = &new_project.name { if let Some(name) = &new_project.name {
@@ -353,8 +360,7 @@ pub async fn project_edit(
&& status.can_be_requested()) && status.can_be_requested())
{ {
return Err(ApiError::CustomAuthentication( return Err(ApiError::CustomAuthentication(
"You don't have permission to set this status!" "You don't have permission to set this status!".to_string(),
.to_string(),
)); ));
} }
@@ -381,9 +387,7 @@ pub async fn project_edit(
.insert(project_item.inner.id.into()); .insert(project_item.inner.id.into());
} }
if status.is_approved() if status.is_approved() && !project_item.inner.status.is_approved() {
&& !project_item.inner.status.is_approved()
{
sqlx::query!( sqlx::query!(
" "
UPDATE mods UPDATE mods
@@ -395,10 +399,9 @@ pub async fn project_edit(
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
} }
if status.is_searchable() && !project_item.inner.webhook_sent { if status.is_searchable() && !project_item.inner.webhook_sent {
if let Ok(webhook_url) = if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") {
dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
{
crate::util::webhook::send_discord_webhook( crate::util::webhook::send_discord_webhook(
project_item.inner.id.into(), project_item.inner.id.into(),
&pool, &pool,
@@ -423,9 +426,7 @@ pub async fn project_edit(
} }
if user.role.is_mod() { if user.role.is_mod() {
if let Ok(webhook_url) = if let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK") {
dotenvy::var("MODERATION_SLACK_WEBHOOK")
{
crate::util::webhook::send_slack_webhook( crate::util::webhook::send_slack_webhook(
project_item.inner.id.into(), project_item.inner.id.into(),
&pool, &pool,
@@ -496,20 +497,6 @@ pub async fn project_edit(
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
if project_item.inner.status.is_searchable()
&& !status.is_searchable()
{
remove_documents(
&project_item
.versions
.into_iter()
.map(|x| x.into())
.collect::<Vec<_>>(),
&search_config,
)
.await?;
}
} }
if let Some(requested_status) = &new_project.requested_status { if let Some(requested_status) = &new_project.requested_status {
@@ -619,8 +606,7 @@ pub async fn project_edit(
)); ));
} }
let slug_project_id_option: Option<u64> = let slug_project_id_option: Option<u64> = parse_base62(slug).ok();
parse_base62(slug).ok();
if let Some(slug_project_id) = slug_project_id_option { if let Some(slug_project_id) = slug_project_id_option {
let results = sqlx::query!( let results = sqlx::query!(
" "
@@ -633,20 +619,14 @@ pub async fn project_edit(
if results.exists.unwrap_or(true) { if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput( return Err(ApiError::InvalidInput(
"Slug collides with other project's id!" "Slug collides with other project's id!".to_string(),
.to_string(),
)); ));
} }
} }
// Make sure the new slug is different from the old one // Make sure the new slug is different from the old one
// We are able to unwrap here because the slug is always set // We are able to unwrap here because the slug is always set
if !slug.eq(&project_item if !slug.eq(&project_item.inner.slug.clone().unwrap_or_default()) {
.inner
.slug
.clone()
.unwrap_or_default())
{
let results = sqlx::query!( let results = sqlx::query!(
" "
SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1)) SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))
@@ -658,8 +638,7 @@ pub async fn project_edit(
if results.exists.unwrap_or(true) { if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput( return Err(ApiError::InvalidInput(
"Slug collides with other project's id!" "Slug collides with other project's id!".to_string(),
.to_string(),
)); ));
} }
} }
@@ -709,6 +688,7 @@ pub async fn project_edit(
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
} }
if let Some(links) = &new_project.link_urls { if let Some(links) = &new_project.link_urls {
if !links.is_empty() { if !links.is_empty() {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) { if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
@@ -718,8 +698,7 @@ pub async fn project_edit(
)); ));
} }
let ids_to_delete = let ids_to_delete = links.keys().cloned().collect::<Vec<String>>();
links.keys().cloned().collect::<Vec<String>>();
// Deletes all links from hashmap- either will be deleted or be replaced // Deletes all links from hashmap- either will be deleted or be replaced
sqlx::query!( sqlx::query!(
" "
@@ -742,14 +721,12 @@ pub async fn project_edit(
&mut *transaction, &mut *transaction,
) )
.await? .await?
.ok_or_else( .ok_or_else(|| {
|| {
ApiError::InvalidInput(format!( ApiError::InvalidInput(format!(
"Platform {} does not exist.", "Platform {} does not exist.",
platform.clone() platform.clone()
)) ))
}, })?;
)?;
sqlx::query!( sqlx::query!(
" "
INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)
@@ -789,8 +766,7 @@ pub async fn project_edit(
.await?; .await?;
} }
if let Some(moderation_message_body) = if let Some(moderation_message_body) = &new_project.moderation_message_body
&new_project.moderation_message_body
{ {
if !user.role.is_mod() if !user.role.is_mod()
&& (!project_item.inner.status.is_approved() && (!project_item.inner.status.is_approved()
@@ -836,8 +812,7 @@ pub async fn project_edit(
.await?; .await?;
} }
if let Some(monetization_status) = &new_project.monetization_status if let Some(monetization_status) = &new_project.monetization_status {
{
if !perms.contains(ProjectPermissions::EDIT_DETAILS) { if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the monetization status of this project!" "You do not have the permissions to edit the monetization status of this project!"
@@ -845,8 +820,7 @@ pub async fn project_edit(
)); ));
} }
if (*monetization_status if (*monetization_status == MonetizationStatus::ForceDemonetized
== MonetizationStatus::ForceDemonetized
|| project_item.inner.monetization_status || project_item.inner.monetization_status
== MonetizationStatus::ForceDemonetized) == MonetizationStatus::ForceDemonetized)
&& !user.role.is_mod() && !user.role.is_mod()
@@ -891,6 +865,7 @@ pub async fn project_edit(
.await?; .await?;
transaction.commit().await?; transaction.commit().await?;
db_models::DBProject::clear_cache( db_models::DBProject::clear_cache(
project_item.inner.id, project_item.inner.id,
project_item.inner.slug, project_item.inner.slug,
@@ -899,15 +874,23 @@ pub async fn project_edit(
) )
.await?; .await?;
// Remove no longer searchable projects from search index
if let (true, Some(false)) = (
project_item.inner.status.is_searchable(),
new_project.status.map(|status| status.is_searchable()),
) {
remove_documents(
&project_item
.versions
.into_iter()
.map(|x| x.into())
.collect::<Vec<_>>(),
&search_config,
)
.await?;
}
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::CustomAuthentication(
"You do not have permission to edit this project!".to_string(),
))
}
} else {
Err(ApiError::NotFound)
}
} }
pub async fn edit_project_categories( pub async fn edit_project_categories(

View File

@@ -958,7 +958,7 @@ pub async fn version_delete(
) )
.await?; .await?;
transaction.commit().await?; transaction.commit().await?;
remove_documents(&[version.inner.id.into()], &search_config).await?;
database::models::DBProject::clear_cache( database::models::DBProject::clear_cache(
version.inner.project_id, version.inner.project_id,
None, None,
@@ -966,6 +966,7 @@ pub async fn version_delete(
&redis, &redis,
) )
.await?; .await?;
remove_documents(&[version.inner.id.into()], &search_config).await?;
if result.is_some() { if result.is_some() {
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))

View File

@@ -1,9 +1,13 @@
/// This module is used for the indexing from any source. /// This module is used for the indexing from any source.
pub mod local_import; pub mod local_import;
use std::time::Duration;
use crate::database::redis::RedisPool; use crate::database::redis::RedisPool;
use crate::search::{SearchConfig, UploadSearchProject}; use crate::search::{SearchConfig, UploadSearchProject};
use ariadne::ids::base62_impl::to_base62; use ariadne::ids::base62_impl::to_base62;
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use local_import::index_local; use local_import::index_local;
use meilisearch_sdk::client::{Client, SwapIndexes}; use meilisearch_sdk::client::{Client, SwapIndexes};
use meilisearch_sdk::indexes::Index; use meilisearch_sdk::indexes::Index;
@@ -11,6 +15,7 @@ use meilisearch_sdk::settings::{PaginationSetting, Settings};
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use thiserror::Error; use thiserror::Error;
use tracing::info; use tracing::info;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum IndexingError { pub enum IndexingError {
#[error("Error while connecting to the MeiliSearch database")] #[error("Error while connecting to the MeiliSearch database")]
@@ -41,12 +46,30 @@ pub async fn remove_documents(
let mut indexes_next = get_indexes_for_indexing(config, true).await?; let mut indexes_next = get_indexes_for_indexing(config, true).await?;
indexes.append(&mut indexes_next); indexes.append(&mut indexes_next);
for index in indexes { let client = config.make_client()?;
let client = &client;
let mut deletion_tasks = FuturesUnordered::new();
for index in &indexes {
deletion_tasks.push(async move {
// After being successfully submitted, Meilisearch tasks are executed
// asynchronously, so wait some time for them to complete
index index
.delete_documents( .delete_documents(
&ids.iter().map(|x| to_base62(x.0)).collect::<Vec<_>>(), &ids.iter().map(|x| to_base62(x.0)).collect::<Vec<_>>(),
) )
.await?; .await?
.wait_for_completion(
client,
None,
Some(Duration::from_secs(15)),
)
.await
});
}
while let Some(result) = deletion_tasks.next().await {
result?;
} }
Ok(()) Ok(())

View File

@@ -151,22 +151,7 @@ async fn index_swaps() {
test_env.api.remove_project("alpha", USER_USER_PAT).await; test_env.api.remove_project("alpha", USER_USER_PAT).await;
assert_status!(&resp, StatusCode::NO_CONTENT); assert_status!(&resp, StatusCode::NO_CONTENT);
// Deletions should not be indexed immediately // We should wait for deletions to be indexed
let projects = test_env
.api
.search_deserialized(
None,
Some(json!([["categories:fabric"]])),
USER_USER_PAT,
)
.await;
assert_eq!(projects.total_hits, 1);
assert!(projects.hits[0].slug.as_ref().unwrap().contains("alpha"));
// But when we reindex, it should be gone
let resp = test_env.api.reset_search_index().await;
assert_status!(&resp, StatusCode::NO_CONTENT);
let projects = test_env let projects = test_env
.api .api
.search_deserialized( .search_deserialized(
@@ -177,7 +162,7 @@ async fn index_swaps() {
.await; .await;
assert_eq!(projects.total_hits, 0); assert_eq!(projects.total_hits, 0);
// Reindex again, should still be gone // When we reindex, it should be still gone
let resp = test_env.api.reset_search_index().await; let resp = test_env.api.reset_search_index().await;
assert_status!(&resp, StatusCode::NO_CONTENT); assert_status!(&resp, StatusCode::NO_CONTENT);