Compare commits

...
Author SHA1 Message Date
aecsocket d760c173a4 Add dependent projects route 2026-06-02 15:14:00 +01:00
4 changed files with 180 additions and 3 deletions
@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT DISTINCT d.mod_dependency_id as \"mod_dependency_id!\", dependent_version.mod_id dependent_project_id, d.dependency_type\n FROM dependencies d\n INNER JOIN versions dependent_version ON dependent_version.id = d.dependent_id\n WHERE d.mod_dependency_id = ANY($1)\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "mod_dependency_id!",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "dependent_project_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "dependency_type",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8Array"
]
},
"nullable": [
true,
false,
false
]
},
"hash": "7279d76dedd38306cba783844c84ff763aabf70eb8091a40c4ec9535b0d44a0c"
}
@@ -0,0 +1,2 @@
CREATE INDEX dependencies_mod_dependency_id
ON dependencies (mod_dependency_id);
+9 -1
View File
@@ -928,7 +928,15 @@ impl VersionType {
}
#[derive(
Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, utoipa::ToSchema,
Serialize,
Deserialize,
Copy,
Clone,
Debug,
PartialEq,
Eq,
Hash,
utoipa::ToSchema,
)]
#[serde(rename_all = "lowercase")]
pub enum DependencyType {
+135 -2
View File
@@ -1,5 +1,5 @@
use std::any::type_name;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::auth::checks::{filter_visible_versions, is_visible_project};
@@ -55,7 +55,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
pub fn utoipa_config(
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
) {
cfg.service(project_get)
cfg.service(project_dependents_get)
.service(project_get)
.service(project_get_check)
.service(project_delete)
.service(project_edit)
@@ -1260,6 +1261,138 @@ pub struct DependencyInfo {
pub versions: Vec<models::projects::Version>,
}
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct ProjectDependentsRequest {
pub ids: Vec<ProjectId>,
}
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct ProjectDependentsResponse {
pub dependents: HashMap<ProjectId, HashSet<DependentInfo>>,
pub projects: HashMap<ProjectId, Project>,
}
#[derive(Serialize, Deserialize, utoipa::ToSchema, PartialEq, Eq, Hash)]
pub struct DependentInfo {
pub dependent_project_id: ProjectId,
pub dependency_type: models::projects::DependencyType,
}
#[utoipa::path]
#[post("/dependents")]
pub async fn project_dependents_get(
req: HttpRequest,
web::Json(projects): web::Json<ProjectDependentsRequest>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<web::Json<ProjectDependentsResponse>, ApiError> {
let user_option = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::PROJECT_READ,
)
.await
.map(|x| x.1)
.ok();
let requested_project_ids = projects
.ids
.iter()
.copied()
.map(db_ids::DBProjectId::from)
.collect::<Vec<_>>();
let requested_projects = db_models::DBProject::get_many_ids(
&requested_project_ids,
&**pool,
&redis,
)
.await
.wrap_internal_err("failed to fetch requested projects")?;
let visible_requested_projects =
filter_visible_projects(requested_projects, &user_option, &pool, false)
.await
.wrap_internal_err("failed to filter requested projects")?;
let project_ids = visible_requested_projects
.iter()
.map(|project| db_ids::DBProjectId::from(project.id).0)
.collect::<Vec<_>>();
let rows = sqlx::query!(
r#"
SELECT DISTINCT d.mod_dependency_id as "mod_dependency_id!", dependent_version.mod_id dependent_project_id, d.dependency_type
FROM dependencies d
INNER JOIN versions dependent_version ON dependent_version.id = d.dependent_id
WHERE d.mod_dependency_id = ANY($1)
"#,
&project_ids,
)
.fetch(&**pool)
.try_collect::<Vec<_>>()
.await
.wrap_internal_err("failed to fetch project dependents")?;
let dependent_project_ids = rows
.iter()
.map(|row| db_ids::DBProjectId(row.dependent_project_id))
.unique()
.collect::<Vec<_>>();
let dependent_projects = db_models::DBProject::get_many_ids(
&dependent_project_ids,
&**pool,
&redis,
)
.await
.wrap_internal_err("failed to fetch dependent projects")?;
let visible_projects =
filter_visible_projects(dependent_projects, &user_option, &pool, false)
.await
.wrap_internal_err("failed to filter dependent projects")?;
let visible_project_ids = visible_projects
.iter()
.map(|project| db_ids::DBProjectId::from(project.id))
.collect::<HashSet<_>>();
let mut dependents = visible_requested_projects
.iter()
.map(|project| project.id)
.map(|project_id| (project_id, HashSet::new()))
.collect::<HashMap<_, _>>();
for row in rows {
let dependent_project_id =
db_ids::DBProjectId(row.dependent_project_id);
if visible_project_ids.contains(&dependent_project_id) {
dependents
.entry(ProjectId::from(db_ids::DBProjectId(
row.mod_dependency_id,
)))
.or_default()
.insert(DependentInfo {
dependent_project_id: ProjectId::from(dependent_project_id),
dependency_type:
models::projects::DependencyType::from_string(
&row.dependency_type,
),
});
}
}
let projects = visible_projects
.into_iter()
.map(|project| (project.id, project))
.collect();
Ok(web::Json(ProjectDependentsResponse {
dependents,
projects,
}))
}
#[utoipa::path]
#[get("/{project_id}/dependencies")]
pub async fn dependency_list(