Skip to content

Commit 430b509

Browse files
committed
feat(labrinth): dependents api endpoint
1 parent 1b1d416 commit 430b509

File tree

9 files changed

+243
-5
lines changed

9 files changed

+243
-5
lines changed

apps/labrinth/.sqlx/query-7c418e474c911332e6425bbb42775b14b5b9edbb23ede6da63d996973d06bf6c.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/labrinth/src/database/models/project_item.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::hash::Hash;
1919
pub const PROJECTS_NAMESPACE: &str = "projects";
2020
pub const PROJECTS_SLUGS_NAMESPACE: &str = "projects_slugs";
2121
const PROJECTS_DEPENDENCIES_NAMESPACE: &str = "projects_dependencies";
22+
const PROJECTS_DEPENDENTS_NAMESPACE: &str = "projects_dependents";
2223

2324
#[derive(Clone, Debug, Serialize, Deserialize)]
2425
pub struct LinkUrl {
@@ -343,8 +344,14 @@ impl DBProject {
343344
let project = Self::get_id(id, &mut **transaction, redis).await?;
344345

345346
if let Some(project) = project {
346-
DBProject::clear_cache(id, project.inner.slug, Some(true), redis)
347-
.await?;
347+
DBProject::clear_cache(
348+
id,
349+
project.inner.slug,
350+
Some(true),
351+
Some(true),
352+
redis,
353+
)
354+
.await?;
348355

349356
sqlx::query!(
350357
"
@@ -932,10 +939,60 @@ impl DBProject {
932939
Ok(dependencies)
933940
}
934941

942+
pub async fn get_dependents<'a, E>(
943+
id: DBProjectId,
944+
exec: E,
945+
redis: &RedisPool,
946+
) -> Result<Vec<(DBVersionId, DBProjectId)>, DatabaseError>
947+
where
948+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
949+
{
950+
type Dependents = Vec<(DBVersionId, DBProjectId)>;
951+
952+
let mut redis = redis.connect().await?;
953+
954+
let dependents = redis
955+
.get_deserialized_from_json::<Dependents>(
956+
PROJECTS_DEPENDENTS_NAMESPACE,
957+
&id.0.to_string(),
958+
)
959+
.await?;
960+
if let Some(dependents) = dependents {
961+
return Ok(dependents);
962+
}
963+
964+
// SELECT d.dependency_id, COALESCE(vd.mod_id, 0) mod_id, d.mod_dependency_id
965+
let dependents: Dependents = sqlx::query!(
966+
"
967+
SELECT DISTINCT version.id version_id, mod.id FROM versions version
968+
INNER JOIN mods mod ON version.mod_id = mod.id
969+
INNER JOIN dependencies d ON version.id = d.dependent_id
970+
WHERE mod.status = 'approved' AND d.mod_dependency_id = $1
971+
ORDER BY mod.id;
972+
",
973+
id as DBProjectId
974+
)
975+
.fetch(exec)
976+
.map_ok(|x| (DBVersionId(x.version_id), DBProjectId(x.id)))
977+
.try_collect::<Dependents>()
978+
.await?;
979+
980+
redis
981+
.set_serialized_to_json(
982+
PROJECTS_DEPENDENTS_NAMESPACE,
983+
id.0,
984+
&dependents,
985+
None,
986+
)
987+
.await?;
988+
Ok(dependents)
989+
}
990+
935991
pub async fn clear_cache(
936992
id: DBProjectId,
937993
slug: Option<String>,
938994
clear_dependencies: Option<bool>,
995+
clear_dependents: Option<bool>,
939996
redis: &RedisPool,
940997
) -> Result<(), DatabaseError> {
941998
let mut redis = redis.connect().await?;
@@ -952,6 +1009,14 @@ impl DBProject {
9521009
None
9531010
},
9541011
),
1012+
(
1013+
PROJECTS_DEPENDENTS_NAMESPACE,
1014+
if clear_dependents.unwrap_or(false) {
1015+
Some(id.0.to_string())
1016+
} else {
1017+
None
1018+
},
1019+
),
9551020
])
9561021
.await?;
9571022
Ok(())

apps/labrinth/src/database/models/version_item.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ impl DBVersion {
451451
DBProjectId(project_id.mod_id),
452452
None,
453453
None,
454+
None,
454455
redis,
455456
)
456457
.await?;

apps/labrinth/src/queue/moderation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ impl AutomatedModerationQueue {
704704
project.inner.id,
705705
project.inner.slug.clone(),
706706
None,
707+
None,
707708
&redis,
708709
)
709710
.await?;

apps/labrinth/src/routes/v2/projects.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
4545
web::scope("{project_id}")
4646
.service(super::versions::version_list)
4747
.service(super::versions::version_project_get)
48-
.service(dependency_list),
48+
.service(dependency_list)
49+
.service(dependents_list),
4950
),
5051
);
5152
}
@@ -306,6 +307,52 @@ pub async fn dependency_list(
306307
}
307308
}
308309

310+
#[get("dependents")]
311+
pub async fn dependents_list(
312+
req: HttpRequest,
313+
info: web::Path<(String,)>,
314+
pool: web::Data<PgPool>,
315+
redis: web::Data<RedisPool>,
316+
session_queue: web::Data<AuthQueue>,
317+
) -> Result<HttpResponse, ApiError> {
318+
// TODO: tests, probably
319+
let response = v3::projects::dependents_list(
320+
req,
321+
info,
322+
pool.clone(),
323+
redis.clone(),
324+
session_queue,
325+
)
326+
.await
327+
.or_else(v2_reroute::flatten_404_error)?;
328+
329+
match v2_reroute::extract_ok_json::<
330+
crate::routes::v3::projects::DependencyInfo,
331+
>(response)
332+
.await
333+
{
334+
Ok(dependency_info) => {
335+
let converted_projects = LegacyProject::from_many(
336+
dependency_info.projects,
337+
&**pool,
338+
&redis,
339+
)
340+
.await?;
341+
let converted_versions = dependency_info
342+
.versions
343+
.into_iter()
344+
.map(LegacyVersion::from)
345+
.collect();
346+
347+
Ok(HttpResponse::Ok().json(DependencyInfo {
348+
projects: converted_projects,
349+
versions: converted_versions,
350+
}))
351+
}
352+
Err(response) => Ok(response),
353+
}
354+
}
355+
309356
#[derive(Serialize, Deserialize, Validate)]
310357
pub struct EditProject {
311358
#[validate(

apps/labrinth/src/routes/v3/organizations.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ pub async fn organization_projects_add(
830830
project_item.inner.id,
831831
project_item.inner.slug,
832832
None,
833+
None,
833834
&redis,
834835
)
835836
.await?;
@@ -1018,6 +1019,7 @@ pub async fn organization_projects_remove(
10181019
project_item.inner.id,
10191020
project_item.inner.slug,
10201021
None,
1022+
None,
10211023
&redis,
10221024
)
10231025
.await?;

apps/labrinth/src/routes/v3/projects.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
7373
"version/{slug}",
7474
web::get().to(super::versions::version_project_get),
7575
)
76-
.route("dependencies", web::get().to(dependency_list)),
76+
.route("dependencies", web::get().to(dependency_list))
77+
.route("dependents", web::get().to(dependents_list)),
7778
),
7879
);
7980
}
@@ -895,6 +896,7 @@ pub async fn project_edit(
895896
project_item.inner.id,
896897
project_item.inner.slug,
897898
None,
899+
None,
898900
&redis,
899901
)
900902
.await?;
@@ -1094,6 +1096,82 @@ pub async fn dependency_list(
10941096
}
10951097
}
10961098

1099+
pub async fn dependents_list(
1100+
req: HttpRequest,
1101+
info: web::Path<(String,)>,
1102+
pool: web::Data<PgPool>,
1103+
redis: web::Data<RedisPool>,
1104+
session_queue: web::Data<AuthQueue>,
1105+
) -> Result<HttpResponse, ApiError> {
1106+
let string = info.into_inner().0;
1107+
1108+
let result = db_models::DBProject::get(&string, &**pool, &redis).await?;
1109+
1110+
let user_option = get_user_from_headers(
1111+
&req,
1112+
&**pool,
1113+
&redis,
1114+
&session_queue,
1115+
Scopes::PROJECT_READ,
1116+
)
1117+
.await
1118+
.map(|x| x.1)
1119+
.ok();
1120+
1121+
if let Some(project) = result {
1122+
if !is_visible_project(&project.inner, &user_option, &pool, false)
1123+
.await?
1124+
{
1125+
return Err(ApiError::NotFound);
1126+
}
1127+
1128+
let dependents = database::DBProject::get_dependents(
1129+
project.inner.id,
1130+
&**pool,
1131+
&redis,
1132+
)
1133+
.await?;
1134+
let project_ids =
1135+
dependents.iter().map(|x| x.1).unique().collect::<Vec<_>>();
1136+
1137+
let dep_version_ids = dependents
1138+
.iter()
1139+
.map(|x| x.0)
1140+
.unique()
1141+
.collect::<Vec<db_models::DBVersionId>>();
1142+
let (projects_result, versions_result) = futures::future::try_join(
1143+
database::DBProject::get_many_ids(&project_ids, &**pool, &redis),
1144+
database::DBVersion::get_many(&dep_version_ids, &**pool, &redis),
1145+
)
1146+
.await?;
1147+
1148+
let mut projects = filter_visible_projects(
1149+
projects_result,
1150+
&user_option,
1151+
&pool,
1152+
false,
1153+
)
1154+
.await?;
1155+
let mut versions = filter_visible_versions(
1156+
versions_result,
1157+
&user_option,
1158+
&pool,
1159+
&redis,
1160+
)
1161+
.await?;
1162+
1163+
projects.sort_by(|a, b| b.published.cmp(&a.published));
1164+
projects.dedup_by(|a, b| a.id == b.id);
1165+
1166+
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
1167+
versions.dedup_by(|a, b| a.id == b.id);
1168+
1169+
Ok(HttpResponse::Ok().json(DependencyInfo { projects, versions }))
1170+
} else {
1171+
Err(ApiError::NotFound)
1172+
}
1173+
}
1174+
10971175
pub struct CategoryChanges<'a> {
10981176
pub categories: &'a Option<Vec<String>>,
10991177
pub add_categories: &'a Option<Vec<String>>,
@@ -1328,6 +1406,7 @@ pub async fn projects_edit(
13281406
project.inner.id,
13291407
project.inner.slug,
13301408
None,
1409+
None,
13311410
&redis,
13321411
)
13331412
.await?;
@@ -1521,6 +1600,7 @@ pub async fn project_icon_edit(
15211600
project_item.inner.id,
15221601
project_item.inner.slug,
15231602
None,
1603+
None,
15241604
&redis,
15251605
)
15261606
.await?;
@@ -1610,6 +1690,7 @@ pub async fn delete_project_icon(
16101690
project_item.inner.id,
16111691
project_item.inner.slug,
16121692
None,
1693+
None,
16131694
&redis,
16141695
)
16151696
.await?;
@@ -1765,6 +1846,7 @@ pub async fn add_gallery_item(
17651846
project_item.inner.id,
17661847
project_item.inner.slug,
17671848
None,
1849+
None,
17681850
&redis,
17691851
)
17701852
.await?;
@@ -1948,6 +2030,7 @@ pub async fn edit_gallery_item(
19482030
project_item.inner.id,
19492031
project_item.inner.slug,
19502032
None,
2033+
None,
19512034
&redis,
19522035
)
19532036
.await?;
@@ -2062,6 +2145,7 @@ pub async fn delete_gallery_item(
20622145
project_item.inner.id,
20632146
project_item.inner.slug,
20642147
None,
2148+
None,
20652149
&redis,
20662150
)
20672151
.await?;

apps/labrinth/src/routes/v3/version_creation.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,14 @@ async fn version_create_inner(
513513
}
514514
}
515515

516-
models::DBProject::clear_cache(project_id, None, Some(true), redis).await?;
516+
models::DBProject::clear_cache(
517+
project_id,
518+
None,
519+
Some(true),
520+
Some(true),
521+
redis,
522+
)
523+
.await?;
517524

518525
let project_status = sqlx::query!(
519526
"SELECT status FROM mods WHERE id = $1",

0 commit comments

Comments
 (0)