Skip to content

Commit b1972a6

Browse files
graylikemeclaude
andcommitted
feat: add optional rulesLevel filter to chassis.variants resolver
Extracts variant query into db::units::get_variants_by_chassis() with optional cumulative rulesLevel filter, so clients can filter variants server-side instead of fetching all and filtering on the client. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2ed7661 commit b1972a6

5 files changed

Lines changed: 84 additions & 146 deletions

File tree

.sqlx/query-68d94e0967f25365ccba20501b6934bedfb2a3cb767cc467e180c1fb93a79059.json

Lines changed: 0 additions & 130 deletions
This file was deleted.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,16 @@ The server starts on `http://localhost:8080`. In debug builds, GraphiQL is avail
204204
}
205205
}
206206

207-
# Chassis with all variants
207+
# Chassis with variants filtered by rules level
208208
{
209209
chassis(slug: "atlas-mech") {
210210
name
211211
unitType
212212
tonnage
213-
variants {
213+
variants(rulesLevel: STANDARD) {
214214
slug
215215
fullName
216+
rulesLevel
216217
bv
217218
introYear
218219
}

crates/api/src/db/units.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,33 @@ pub async fn list_chassis(
265265
Ok(rows)
266266
}
267267

268+
pub async fn get_variants_by_chassis(
269+
pool: &PgPool,
270+
chassis_id: i32,
271+
rules_level: Option<&str>,
272+
) -> Result<Vec<DbUnit>, AppError> {
273+
let mut builder = sqlx::QueryBuilder::<sqlx::Postgres>::new(
274+
r#"SELECT u.id, u.slug, u.chassis_id, u.variant, u.full_name,
275+
u.tech_base::text AS tech_base, u.rules_level::text AS rules_level,
276+
u.tonnage, u.bv, u.cost, u.intro_year, u.extinction_year,
277+
u.reintro_year, u.source_book, u.description,
278+
u.mul_id, u.role, u.clan_name, NULL::bigint AS total_count
279+
FROM units u WHERE u.chassis_id = "#,
280+
);
281+
builder.push_bind(chassis_id);
282+
if let Some(rl) = rules_level {
283+
builder.push(" AND u.rules_level <= ");
284+
builder.push_bind(rl);
285+
builder.push("::rules_level_enum");
286+
}
287+
builder.push(" ORDER BY u.variant");
288+
let rows = builder
289+
.build_query_as::<DbUnit>()
290+
.fetch_all(pool)
291+
.await?;
292+
Ok(rows)
293+
}
294+
268295
pub async fn get_locations(pool: &PgPool, unit_id: i32) -> Result<Vec<DbLocation>, AppError> {
269296
let rows = sqlx::query_as!(
270297
DbLocation,
@@ -732,6 +759,46 @@ mod tests {
732759
assert_eq!(rows[0].slug, "atlas-mech");
733760
}
734761

762+
#[sqlx::test(migrations = "../../migrations")]
763+
async fn variants_rules_level_filter_is_cumulative(pool: PgPool) {
764+
seed_chassis_with_rules_levels(&pool).await;
765+
766+
let atlas_id: i32 =
767+
sqlx::query_scalar("SELECT id FROM unit_chassis WHERE slug = 'atlas-mech'")
768+
.fetch_one(&pool)
769+
.await
770+
.unwrap();
771+
let tw_id: i32 =
772+
sqlx::query_scalar("SELECT id FROM unit_chassis WHERE slug = 'timber-wolf-mech'")
773+
.fetch_one(&pool)
774+
.await
775+
.unwrap();
776+
777+
// Atlas: has introductory (AS7-D) + standard (AS7-K)
778+
// No filter — returns both
779+
let rows = get_variants_by_chassis(&pool, atlas_id, None).await.unwrap();
780+
assert_eq!(rows.len(), 2);
781+
782+
// Introductory — only AS7-D
783+
let rows = get_variants_by_chassis(&pool, atlas_id, Some("introductory")).await.unwrap();
784+
assert_eq!(rows.len(), 1);
785+
assert_eq!(rows[0].slug, "atlas-as7-d");
786+
787+
// Standard — both (introductory + standard <= standard)
788+
let rows = get_variants_by_chassis(&pool, atlas_id, Some("standard")).await.unwrap();
789+
assert_eq!(rows.len(), 2);
790+
791+
// Timber Wolf: only advanced (Prime)
792+
// Standard — none (advanced > standard)
793+
let rows = get_variants_by_chassis(&pool, tw_id, Some("standard")).await.unwrap();
794+
assert_eq!(rows.len(), 0);
795+
796+
// Advanced — returns Prime
797+
let rows = get_variants_by_chassis(&pool, tw_id, Some("advanced")).await.unwrap();
798+
assert_eq!(rows.len(), 1);
799+
assert_eq!(rows[0].slug, "timber-wolf-prime");
800+
}
801+
735802
#[sqlx::test(migrations = "../../migrations")]
736803
async fn combined_bv_and_unit_type_filter(pool: PgPool) {
737804
seed_units_with_bv(&pool).await;

crates/api/src/graphql/types/unit.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
},
1212
types::construction::{
1313
ArmorTypeGql, CockpitTypeGql, EngineTypeGql, GyroTypeGql, HeatsinkTypeGql,
14-
MyomerTypeGql, StructureTypeGql,
14+
MyomerTypeGql, RulesLevelFilter, StructureTypeGql,
1515
},
1616
},
1717
state::AppState,
@@ -114,20 +114,19 @@ impl UnitChassisGql {
114114
}
115115

116116
/// All unit variants belonging to this chassis, ordered by variant designation.
117+
/// Optionally filtered by maximum rules level (cumulative).
117118
#[graphql(complexity = 5)]
118-
async fn variants(&self, ctx: &Context<'_>) -> Result<Vec<UnitGql>, AppError> {
119+
async fn variants(
120+
&self,
121+
ctx: &Context<'_>,
122+
#[graphql(desc = "Filter variants by maximum rules level (cumulative). E.g. ADVANCED includes introductory, standard, and advanced.")] rules_level: Option<RulesLevelFilter>,
123+
) -> Result<Vec<UnitGql>, AppError> {
119124
let state = ctx.data::<AppState>().unwrap();
120-
let rows = sqlx::query_as!(
121-
DbUnit,
122-
r#"SELECT u.id, u.slug, u.chassis_id, u.variant, u.full_name,
123-
u.tech_base::text AS "tech_base!", u.rules_level::text AS "rules_level!",
124-
u.tonnage, u.bv, u.cost, u.intro_year, u.extinction_year,
125-
u.reintro_year, u.source_book, u.description,
126-
u.mul_id, u.role, u.clan_name, NULL::bigint AS total_count
127-
FROM units u WHERE u.chassis_id = $1 ORDER BY u.variant"#,
128-
self.0.id
125+
let rows = crate::db::units::get_variants_by_chassis(
126+
&state.pool,
127+
self.0.id,
128+
rules_level.map(|r| r.as_db_str()),
129129
)
130-
.fetch_all(&state.pool)
131130
.await?;
132131
Ok(rows.into_iter().map(UnitGql).collect())
133132
}

crates/api/src/handlers/llms_txt.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,19 +384,20 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
384384
}}
385385
```
386386
387-
### Get chassis with all its variants
387+
### Get chassis with variants filtered by rules level
388388
```graphql
389389
{{
390390
chassis(slug: "atlas-mech") {{
391391
name
392392
unitType
393393
techBase
394394
tonnage
395-
variants {{
395+
variants(rulesLevel: STANDARD) {{
396396
slug
397397
variant
398398
fullName
399399
techBase
400+
rulesLevel
400401
bv
401402
introYear
402403
}}

0 commit comments

Comments
 (0)