From 2afe0063562c631d3f048868ebe3fe713f5b8cfa Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:20:07 +0100 Subject: [PATCH 1/3] fix: speed up issue detail queries --- ...1794cfb4dd6f8bf54985c182840dfeffd05f8.json | 34 ++++++++++ ...e04d15b222762a4b5f2102b404e06f5285f4a.json | 34 ---------- .../internal/delphi/tech_review_queue.rs | 63 ++++++++++--------- 3 files changed, 69 insertions(+), 62 deletions(-) create mode 100644 apps/labrinth/.sqlx/query-6f1e755a90bdc960e164785b16e1794cfb4dd6f8bf54985c182840dfeffd05f8.json delete mode 100644 apps/labrinth/.sqlx/query-9f8d7940181e60f6867f77f6e84e04d15b222762a4b5f2102b404e06f5285f4a.json diff --git a/apps/labrinth/.sqlx/query-6f1e755a90bdc960e164785b16e1794cfb4dd6f8bf54985c182840dfeffd05f8.json b/apps/labrinth/.sqlx/query-6f1e755a90bdc960e164785b16e1794cfb4dd6f8bf54985c182840dfeffd05f8.json new file mode 100644 index 00000000000..15252c13033 --- /dev/null +++ b/apps/labrinth/.sqlx/query-6f1e755a90bdc960e164785b16e1794cfb4dd6f8bf54985c182840dfeffd05f8.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n requested.project_id AS \"project_id!: DBProjectId\",\n EXISTS (\n SELECT 1\n FROM versions version\n INNER JOIN files file ON file.version_id = version.id\n INNER JOIN delphi_reports report ON report.file_id = file.id\n INNER JOIN delphi_report_issues issue ON issue.report_id = report.id\n INNER JOIN delphi_report_issue_details detail ON detail.issue_id = issue.id\n WHERE version.mod_id = requested.project_id\n AND NOT EXISTS (\n SELECT 1\n FROM delphi_reports newer_report\n WHERE newer_report.file_id = report.file_id\n AND newer_report.delphi_version > report.delphi_version\n )\n ) AS \"has_details!\",\n CASE WHEN EXISTS (\n SELECT 1\n FROM delphi_tech_review_queue queued\n WHERE queued.project_id = requested.project_id\n ) THEN FALSE\n ELSE EXISTS (\n SELECT 1\n FROM delphi_issue_details_with_statuses detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n WHERE detail.project_id = requested.project_id\n AND detail.status IN ('pending', 'unsafe')\n AND detail.severity != 'hidden'\n AND NOT EXISTS (\n SELECT 1\n FROM delphi_reports newer_report\n WHERE newer_report.file_id = report.file_id\n AND newer_report.delphi_version > report.delphi_version\n )\n ) END AS \"needs_review!\"\n FROM unnest($1::bigint[]) AS requested(project_id)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "project_id!: DBProjectId", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "has_details!", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "needs_review!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + null, + null, + null + ] + }, + "hash": "6f1e755a90bdc960e164785b16e1794cfb4dd6f8bf54985c182840dfeffd05f8" +} diff --git a/apps/labrinth/.sqlx/query-9f8d7940181e60f6867f77f6e84e04d15b222762a4b5f2102b404e06f5285f4a.json b/apps/labrinth/.sqlx/query-9f8d7940181e60f6867f77f6e84e04d15b222762a4b5f2102b404e06f5285f4a.json deleted file mode 100644 index 93b73cb3648..00000000000 --- a/apps/labrinth/.sqlx/query-9f8d7940181e60f6867f77f6e84e04d15b222762a4b5f2102b404e06f5285f4a.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n WITH current_details AS (\n SELECT\n detail.project_id,\n detail.status,\n detail.severity\n FROM delphi_issue_details_with_statuses detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report\n ON report.id = issue.report_id\n AND NOT EXISTS (\n SELECT 1\n FROM delphi_reports newer_report\n WHERE\n newer_report.file_id = report.file_id\n AND newer_report.delphi_version > report.delphi_version\n )\n )\n SELECT\n requested.project_id AS \"project_id!: DBProjectId\",\n BOOL_OR(current_details.project_id IS NOT NULL) AS \"has_details!\",\n COALESCE(\n BOOL_OR(\n current_details.status IN ('pending', 'unsafe')\n AND current_details.severity != 'hidden'\n ),\n FALSE\n ) AS \"needs_review!\"\n FROM unnest($1::bigint[]) AS requested(project_id)\n LEFT JOIN current_details\n ON current_details.project_id = requested.project_id\n GROUP BY requested.project_id\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "project_id!: DBProjectId", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "has_details!", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "needs_review!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int8Array" - ] - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "9f8d7940181e60f6867f77f6e84e04d15b222762a4b5f2102b404e06f5285f4a" -} diff --git a/apps/labrinth/src/routes/internal/delphi/tech_review_queue.rs b/apps/labrinth/src/routes/internal/delphi/tech_review_queue.rs index 9d57b8b71fd..985db1d3712 100644 --- a/apps/labrinth/src/routes/internal/delphi/tech_review_queue.rs +++ b/apps/labrinth/src/routes/internal/delphi/tech_review_queue.rs @@ -145,37 +145,44 @@ pub async fn sync_projects( let rows = sqlx::query!( r#" - WITH current_details AS ( - SELECT - detail.project_id, - detail.status, - detail.severity - FROM delphi_issue_details_with_statuses detail - INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id - INNER JOIN delphi_reports report - ON report.id = issue.report_id - AND NOT EXISTS ( - SELECT 1 - FROM delphi_reports newer_report - WHERE - newer_report.file_id = report.file_id - AND newer_report.delphi_version > report.delphi_version - ) - ) SELECT requested.project_id AS "project_id!: DBProjectId", - BOOL_OR(current_details.project_id IS NOT NULL) AS "has_details!", - COALESCE( - BOOL_OR( - current_details.status IN ('pending', 'unsafe') - AND current_details.severity != 'hidden' - ), - FALSE - ) AS "needs_review!" + EXISTS ( + SELECT 1 + FROM versions version + INNER JOIN files file ON file.version_id = version.id + INNER JOIN delphi_reports report ON report.file_id = file.id + INNER JOIN delphi_report_issues issue ON issue.report_id = report.id + INNER JOIN delphi_report_issue_details detail ON detail.issue_id = issue.id + WHERE version.mod_id = requested.project_id + AND NOT EXISTS ( + SELECT 1 + FROM delphi_reports newer_report + WHERE newer_report.file_id = report.file_id + AND newer_report.delphi_version > report.delphi_version + ) + ) AS "has_details!", + CASE WHEN EXISTS ( + SELECT 1 + FROM delphi_tech_review_queue queued + WHERE queued.project_id = requested.project_id + ) THEN FALSE + ELSE EXISTS ( + SELECT 1 + FROM delphi_issue_details_with_statuses detail + INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id + INNER JOIN delphi_reports report ON report.id = issue.report_id + WHERE detail.project_id = requested.project_id + AND detail.status IN ('pending', 'unsafe') + AND detail.severity != 'hidden' + AND NOT EXISTS ( + SELECT 1 + FROM delphi_reports newer_report + WHERE newer_report.file_id = report.file_id + AND newer_report.delphi_version > report.delphi_version + ) + ) END AS "needs_review!" FROM unnest($1::bigint[]) AS requested(project_id) - LEFT JOIN current_details - ON current_details.project_id = requested.project_id - GROUP BY requested.project_id "#, &project_ids.iter().map(|id| id.0).collect::>(), ) From 3653bf09047425ddc25d9b7bf29d56dd5b8da646 Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:20:00 +0100 Subject: [PATCH 2/3] fix: allow %2B to redirect as + --- .gitattributes | 2 +- .gitignore | 1 + apps/labrinth/src/routes/mod.rs | 10 +++++-- apps/labrinth/tests/project.rs | 53 +++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 9fd71a11cb5..b3103a1d799 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,7 @@ # Excludes locale files that have been automatically trimmed during prepr # from the diff viewer + diff calculation on GitHub. **/locales/*/index.json linguist-generated=true -!**/locales/en-US/index.json linguist-generated +**/locales/en-US/index.json !linguist-generated # SQLx calculates a checksum of migration scripts at build time to compare # it with the checksum of the applied migration for the same version at diff --git a/.gitignore b/.gitignore index d442251099c..5eecee60aac 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,4 @@ apps/frontend/src/public/robots.txt /test_result.md packages/tooling-config/script-utils/import-sort.js +references/ diff --git a/apps/labrinth/src/routes/mod.rs b/apps/labrinth/src/routes/mod.rs index 132ed5131b1..a424c7aec39 100644 --- a/apps/labrinth/src/routes/mod.rs +++ b/apps/labrinth/src/routes/mod.rs @@ -13,7 +13,7 @@ use crate::util::error::{ApiContext, Context}; use actix_cors::Cors; use actix_files::Files; use actix_web::http::{StatusCode, header}; -use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::{FromRequest, HttpRequest, HttpResponse, web}; use ariadne::ids::base62_impl::parse_base62; use futures::FutureExt; use std::collections::{HashMap, HashSet}; @@ -354,7 +354,13 @@ pub async fn redirect_ref( session_queue: &AuthQueue, required_scopes: Scopes, ) -> Result, ApiError> { - let Some(project_ref) = req.match_info().get(parameter_name) else { + // `match_info` preserves encoded `+`, `/`, and `%`; the path extractor fully decodes them. + let parameters = web::Path::>::extract(req) + .await + .map_err(|err| { + ApiError::Request(eyre::eyre!("decoding project redirect path: {err}")) + })?; + let Some(project_ref) = parameters.get(parameter_name) else { return Ok(None); }; let Some(target_project_id) = resolve_visible_refs( diff --git a/apps/labrinth/tests/project.rs b/apps/labrinth/tests/project.rs index 978638dbd5c..368785c325a 100644 --- a/apps/labrinth/tests/project.rs +++ b/apps/labrinth/tests/project.rs @@ -370,6 +370,59 @@ async fn project_slug_edit_invalidates_redirect_cache() { .await; } +#[actix_rt::test] +async fn project_redirect_accepts_literal_and_encoded_plus() { + with_test_environment_all(None, |env| async move { + let target_id = &env.dummy.project_alpha.project_id; + let target = parse_base62(target_id).unwrap(); + let percent_target_id = &env.dummy.project_beta.project_id; + let percent_target = parse_base62(percent_target_id).unwrap(); + let fixture_sql = format!( + r#"INSERT INTO project_redirects (slug, target_project_id) + VALUES ('redirect+alias', {target}), ('redirect%2balias', {percent_target})"# + ); + sqlx::raw_sql(&fixture_sql) + .execute(&env.db.pool) + .await + .unwrap(); + + for project_ref in [ + "redirect+alias", + "redirect%2Balias", + "redirect%2balias", + "redirect%252Balias", + ] { + for suffix in ["", "/check", "/check?test=foo%2Bbar"] { + let response = env + .api + .get_project( + &format!("{project_ref}{suffix}"), + USER_USER_PAT, + ) + .await; + assert_status!(&response, StatusCode::PERMANENT_REDIRECT); + let location = response + .headers() + .get("location") + .unwrap() + .to_str() + .unwrap(); + let expected_target = if project_ref == "redirect%252Balias" { + percent_target_id + } else { + target_id + }; + assert!( + location.ends_with(&format!( + "/project/{expected_target}{suffix}" + )) + ); + } + } + }) + .await; +} + #[actix_rt::test] async fn project_redirect_cache_preserves_case_sensitive_ids() { with_test_environment_all(None, |env| async move { From 5e10567627ea91227dd28bf4c5308277ad55ce6c Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:33:14 +0100 Subject: [PATCH 3/3] fix extractor --- apps/labrinth/src/routes/mod.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/labrinth/src/routes/mod.rs b/apps/labrinth/src/routes/mod.rs index a424c7aec39..1f55e846d98 100644 --- a/apps/labrinth/src/routes/mod.rs +++ b/apps/labrinth/src/routes/mod.rs @@ -354,13 +354,23 @@ pub async fn redirect_ref( session_queue: &AuthQueue, required_scopes: Scopes, ) -> Result, ApiError> { + let Some(parameter_index) = req + .match_info() + .iter() + .position(|(name, _)| name == parameter_name) + else { + return Ok(None); + }; // `match_info` preserves encoded `+`, `/`, and `%`; the path extractor fully decodes them. - let parameters = web::Path::>::extract(req) - .await - .map_err(|err| { - ApiError::Request(eyre::eyre!("decoding project redirect path: {err}")) - })?; - let Some(project_ref) = parameters.get(parameter_name) else { + let parameters = + web::Path::>::extract(req) + .await + .map_err(|err| { + ApiError::Request(eyre::eyre!( + "decoding project redirect path: {err}" + )) + })?; + let Some(project_ref) = parameters.get(parameter_index) else { return Ok(None); }; let Some(target_project_id) = resolve_visible_refs(