inline loader enum metadata fields

This commit is contained in:
aecsocket
2026-08-04 17:20:20 +01:00
committed by Calum H.
parent 33c19df64b
commit 2308ac1a04
10 changed files with 117 additions and 133 deletions
@@ -117,16 +117,8 @@ impl MinecraftGameVersion {
id: loader_field_enum_value.id, id: loader_field_enum_value.id,
version: loader_field_enum_value.value, version: loader_field_enum_value.value,
created: loader_field_enum_value.created, created: loader_field_enum_value.created,
type_: loader_field_enum_value type_: loader_field_enum_value.ty.unwrap_or_default(),
.metadata major: loader_field_enum_value.major.unwrap_or_default(),
.as_ref()
.map(|metadata| metadata.type_.clone())
.unwrap_or_default(),
major: loader_field_enum_value
.metadata
.as_ref()
.map(|metadata| metadata.major)
.unwrap_or_default(),
} }
} }
} }
@@ -278,13 +278,6 @@ pub struct LoaderFieldEnum {
pub hidable: bool, pub hidable: bool,
} }
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct LoaderFieldEnumValueMetadata {
#[serde(rename = "type")]
pub type_: String,
pub major: bool,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct LoaderFieldEnumValue { pub struct LoaderFieldEnumValue {
pub id: LoaderFieldEnumValueId, pub id: LoaderFieldEnumValueId,
@@ -292,8 +285,9 @@ pub struct LoaderFieldEnumValue {
pub value: String, pub value: String,
pub ordering: Option<i32>, pub ordering: Option<i32>,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
#[serde(flatten)] #[serde(rename = "type")]
pub metadata: Option<LoaderFieldEnumValueMetadata>, pub ty: Option<String>,
pub major: Option<bool>,
} }
impl std::hash::Hash for LoaderFieldEnumValue { impl std::hash::Hash for LoaderFieldEnumValue {
@@ -372,7 +366,8 @@ pub struct QueryLoaderFieldEnumValue {
pub value: String, pub value: String,
pub ordering: Option<i32>, pub ordering: Option<i32>,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub metadata: Option<LoaderFieldEnumValueMetadata>, pub ty: Option<String>,
pub major: Option<bool>,
} }
impl LoaderField { impl LoaderField {
@@ -627,44 +622,50 @@ impl LoaderFieldEnumValue {
where where
E: crate::database::Executor<'a, Database = sqlx::Postgres>, E: crate::database::Executor<'a, Database = sqlx::Postgres>,
{ {
let val = redis.get_cached_keys_raw( let val = redis
LOADER_FIELD_ENUM_VALUES_NAMESPACE, .get_cached_keys_raw(
&loader_field_enum_ids.iter().map(|x| x.0).collect::<Vec<_>>(), LOADER_FIELD_ENUM_VALUES_NAMESPACE,
|loader_field_enum_ids| async move { &loader_field_enum_ids
let values = sqlx::query!( .iter()
r#" .map(|x| x.0)
.collect::<Vec<_>>(),
|loader_field_enum_ids| async move {
let values = sqlx::query!(
r#"
SELECT id, enum_id, value, ordering, SELECT id, enum_id, value, ordering,
metadata AS "metadata?: sqlx::types::Json<LoaderFieldEnumValueMetadata>", metadata->>'type' AS "ty?",
(metadata->>'major')::boolean AS "major?",
created FROM loader_field_enum_values created FROM loader_field_enum_values
WHERE enum_id = ANY($1) WHERE enum_id = ANY($1)
ORDER BY enum_id, ordering, created DESC ORDER BY enum_id, ordering, created DESC
"#, "#,
&loader_field_enum_ids &loader_field_enum_ids
) )
.fetch(exec) .fetch(exec)
.try_fold(DashMap::new(), |acc: DashMap<i32, Vec<LoaderFieldEnumValue>>, c| { .try_fold(
let value = LoaderFieldEnumValue { DashMap::new(),
id: LoaderFieldEnumValueId(c.id), |acc: DashMap<i32, Vec<LoaderFieldEnumValue>>, c| {
enum_id: LoaderFieldEnumId(c.enum_id), let value = LoaderFieldEnumValue {
value: c.value, id: LoaderFieldEnumValueId(c.id),
ordering: c.ordering, enum_id: LoaderFieldEnumId(c.enum_id),
created: c.created, value: c.value,
metadata: c.metadata.map(|metadata| metadata.0), ordering: c.ordering,
}; created: c.created,
ty: c.ty,
major: c.major,
};
acc.entry(c.enum_id) acc.entry(c.enum_id).or_default().push(value);
.or_default()
.push(value);
async move { async move { Ok(acc) }
Ok(acc) },
} )
})
.await?; .await?;
Ok::<_, DatabaseError>(values) Ok::<_, DatabaseError>(values)
}, },
).await?; )
.await?;
Ok(val Ok(val
.into_iter() .into_iter()
@@ -687,12 +688,13 @@ impl LoaderFieldEnumValue {
.into_iter() .into_iter()
.filter(|x| { .filter(|x| {
filter.iter().all(|(key, value)| match key.as_str() { filter.iter().all(|(key, value)| match key.as_str() {
"type" => x.metadata.as_ref().is_some_and(|metadata| { "type" => {
value.as_str() == Some(metadata.type_.as_str()) x.ty.as_deref()
}), .is_some_and(|type_| value.as_str() == Some(type_))
"major" => x.metadata.as_ref().is_some_and(|metadata| { }
value.as_bool() == Some(metadata.major) "major" => x
}), .major
.is_some_and(|major| value.as_bool() == Some(major)),
_ => false, _ => false,
}) })
}) })
@@ -1187,7 +1189,8 @@ impl VersionFieldValue {
value: lfev.value.clone(), value: lfev.value.clone(),
ordering: lfev.ordering, ordering: lfev.ordering,
created: lfev.created, created: lfev.created,
metadata: lfev.metadata.clone(), ty: lfev.ty.clone(),
major: lfev.major,
} }
}), }),
)) ))
@@ -1263,7 +1266,8 @@ impl VersionFieldValue {
value: lfev.value.clone(), value: lfev.value.clone(),
ordering: lfev.ordering, ordering: lfev.ordering,
created: lfev.created, created: lfev.created,
metadata: lfev.metadata.clone(), ty: lfev.ty.clone(),
major: lfev.major,
}) })
}) })
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
@@ -136,7 +136,7 @@ pub struct QueryProductWithPrices {
pub id: DBProductId, pub id: DBProductId,
pub metadata: ProductMetadata, pub metadata: ProductMetadata,
pub unitary: bool, pub unitary: bool,
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(default)]
pub name: Option<String>, pub name: Option<String>,
pub prices: Vec<DBProductPrice>, pub prices: Vec<DBProductPrice>,
} }
@@ -1,6 +1,6 @@
use super::loader_fields::{ use super::loader_fields::{
LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField,
QueryVersionField, VersionField, VersionField,
}; };
use super::{DBUser, ids::*}; use super::{DBUser, ids::*};
use crate::database::models::DatabaseError; use crate::database::models::DatabaseError;
@@ -659,7 +659,8 @@ impl DBProject {
let loader_field_enum_values: Vec<QueryLoaderFieldEnumValue> = sqlx::query!( let loader_field_enum_values: Vec<QueryLoaderFieldEnumValue> = sqlx::query!(
r#" r#"
SELECT DISTINCT id, enum_id, value, ordering, created, SELECT DISTINCT id, enum_id, value, ordering, created,
metadata AS "metadata?: sqlx::types::Json<LoaderFieldEnumValueMetadata>" metadata->>'type' AS "ty?",
(metadata->>'major')::boolean AS "major?"
FROM loader_field_enum_values lfev FROM loader_field_enum_values lfev
WHERE id = ANY($1) WHERE id = ANY($1)
ORDER BY enum_id, ordering, created DESC ORDER BY enum_id, ordering, created DESC
@@ -676,7 +677,8 @@ impl DBProject {
value: m.value, value: m.value,
ordering: m.ordering, ordering: m.ordering,
created: m.created, created: m.created,
metadata: m.metadata.map(|metadata| metadata.0), ty: m.ty,
major: m.major,
}) })
.try_collect() .try_collect()
.await?; .await?;
@@ -1054,6 +1056,5 @@ pub struct ProjectQueryResult {
pub gallery_items: Vec<DBGalleryItem>, pub gallery_items: Vec<DBGalleryItem>,
pub thread_id: DBThreadId, pub thread_id: DBThreadId,
pub aggregate_version_fields: Vec<VersionField>, pub aggregate_version_fields: Vec<VersionField>,
#[serde(flatten)]
pub components: exp::ProjectQuery, pub components: exp::ProjectQuery,
} }
@@ -3,8 +3,7 @@ use super::ids::*;
use super::loader_fields::VersionField; use super::loader_fields::VersionField;
use crate::database::PgTransaction; use crate::database::PgTransaction;
use crate::database::models::loader_fields::{ use crate::database::models::loader_fields::{
LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField,
QueryVersionField,
}; };
use crate::file_hosting::FileHost; use crate::file_hosting::FileHost;
use crate::models::exp; use crate::models::exp;
@@ -707,7 +706,8 @@ impl DBVersion {
let loader_field_enum_values: Vec<QueryLoaderFieldEnumValue> = sqlx::query!( let loader_field_enum_values: Vec<QueryLoaderFieldEnumValue> = sqlx::query!(
r#" r#"
SELECT DISTINCT id, enum_id, value, ordering, created, SELECT DISTINCT id, enum_id, value, ordering, created,
metadata AS "metadata?: sqlx::types::Json<LoaderFieldEnumValueMetadata>" metadata->>'type' AS "ty?",
(metadata->>'major')::boolean AS "major?"
FROM loader_field_enum_values lfev FROM loader_field_enum_values lfev
WHERE id = ANY($1) WHERE id = ANY($1)
ORDER BY enum_id, ordering, created ASC ORDER BY enum_id, ordering, created ASC
@@ -724,7 +724,8 @@ impl DBVersion {
value: m.value, value: m.value,
ordering: m.ordering, ordering: m.ordering,
created: m.created, created: m.created,
metadata: m.metadata.map(|metadata| metadata.0), ty: m.ty,
major: m.major,
}) })
.try_collect() .try_collect()
.await?; .await?;
@@ -1092,7 +1093,6 @@ pub struct VersionQueryResult {
pub project_types: Vec<String>, pub project_types: Vec<String>,
pub games: Vec<String>, pub games: Vec<String>,
pub dependencies: Vec<DependencyQueryResult>, pub dependencies: Vec<DependencyQueryResult>,
#[serde(flatten)]
pub components: exp::VersionQuery, pub components: exp::VersionQuery,
} }
+42 -47
View File
@@ -69,18 +69,15 @@ mod tests {
use crate::database::models::flow_item::DBFlow; use crate::database::models::flow_item::DBFlow;
use crate::database::models::ids::{ use crate::database::models::ids::{
DBNotificationId, DBProductId, DBProductPriceId, DBProjectId, DBTeamId, DBNotificationId, DBProjectId, DBTeamId, DBThreadId, DBUserId,
DBThreadId, DBUserId, DBVersionId, LoaderFieldEnumId, DBVersionId, LoaderFieldEnumId, LoaderFieldEnumValueId, LoaderFieldId,
LoaderFieldEnumValueId, LoaderFieldId, LoaderId, LoaderId,
}; };
use crate::database::models::loader_fields::{ use crate::database::models::loader_fields::{
Loader, LoaderFieldEnumValue, LoaderFieldEnumValueMetadata, Loader, LoaderFieldEnumValue, LoaderMetadata, VersionField,
LoaderMetadata, VersionField, VersionFieldValue, VersionFieldValue,
}; };
use crate::database::models::notification_item::DBNotification; use crate::database::models::notification_item::DBNotification;
use crate::database::models::product_item::{
DBProductPrice, QueryProductWithPrices,
};
use crate::database::models::project_item::{ use crate::database::models::project_item::{
DBProject, ProjectQueryResult, DBProject, ProjectQueryResult,
}; };
@@ -104,20 +101,15 @@ mod tests {
postcard::from_bytes(&serialized).expect("deserializing with postcard") postcard::from_bytes(&serialized).expect("deserializing with postcard")
} }
fn loader_field_enum_value( fn loader_field_enum_value(ty: &str, major: bool) -> LoaderFieldEnumValue {
type_: &str,
major: bool,
) -> LoaderFieldEnumValue {
LoaderFieldEnumValue { LoaderFieldEnumValue {
id: LoaderFieldEnumValueId(1), id: LoaderFieldEnumValueId(1),
enum_id: LoaderFieldEnumId(2), enum_id: LoaderFieldEnumId(2),
value: "1.21.8".to_string(), value: "1.21.8".to_string(),
ordering: None, ordering: None,
created: Utc::now(), created: Utc::now(),
metadata: Some(LoaderFieldEnumValueMetadata { ty: Some(ty.to_string()),
type_: type_.to_string(), major: Some(major),
major,
}),
} }
} }
@@ -211,31 +203,48 @@ mod tests {
} }
#[test] #[test]
fn loader_field_enum_value_metadata_round_trips_with_postcard() { fn loader_field_enum_values_round_trip_with_postcard() {
let metadata_values = [ let metadata_values = [
("snapshot", false), (None, None),
("alpha", false), (Some("snapshot"), Some(false)),
("beta", true), (Some("alpha"), Some(false)),
("release", true), (Some("beta"), Some(true)),
("beta", false), (Some("release"), Some(true)),
("release", false), (Some("beta"), Some(false)),
(Some("release"), Some(false)),
]; ];
assert_eq!( for (ty, major) in metadata_values {
postcard_round_trip(&None::<LoaderFieldEnumValueMetadata>), let enum_value = LoaderFieldEnumValue {
None id: LoaderFieldEnumValueId(1),
); enum_id: LoaderFieldEnumId(2),
value: "1.21.8".to_string(),
for (type_, major) in metadata_values { ordering: None,
let metadata = Some(LoaderFieldEnumValueMetadata { created: Utc::now(),
type_: type_.to_string(), ty: ty.map(str::to_string),
major, major,
}); };
assert_eq!(postcard_round_trip(&metadata), metadata); assert_eq!(postcard_round_trip(&enum_value), enum_value);
} }
} }
#[test]
fn loader_field_enum_value_keeps_flattened_json_layout() {
let enum_value = loader_field_enum_value("release", true);
let json = serde_json::to_value(&enum_value)
.expect("serializing loader field enum value as JSON");
assert_eq!(json.get("type"), Some(&serde_json::json!("release")));
assert_eq!(json.get("major"), Some(&serde_json::json!(true)));
assert!(json.get("metadata").is_none());
assert_eq!(
serde_json::from_value::<LoaderFieldEnumValue>(json)
.expect("deserializing loader field enum value from JSON"),
enum_value
);
}
#[test] #[test]
fn non_enum_version_fields_round_trip_with_postcard() { fn non_enum_version_fields_round_trip_with_postcard() {
let values = [ let values = [
@@ -305,20 +314,6 @@ mod tests {
postcard_round_trip(&exp::ProjectSerial::default()); postcard_round_trip(&exp::ProjectSerial::default());
postcard_round_trip(&exp::ProjectQuery::default()); postcard_round_trip(&exp::ProjectQuery::default());
postcard_round_trip(&db_project()); postcard_round_trip(&db_project());
let product = QueryProductWithPrices {
id: DBProductId(1),
metadata: ProductMetadata::Midas,
unitary: false,
name: None,
prices: vec![DBProductPrice {
id: DBProductPriceId(2),
product_id: DBProductId(1),
prices: Price::OneTime { price: 500 },
currency_code: "USD".to_string(),
}],
};
postcard_round_trip(&product);
} }
#[test] #[test]
+1 -2
View File
@@ -54,7 +54,7 @@ macro_rules! define_project_components {
pub struct ProjectSerial { pub struct ProjectSerial {
$( $(
#[validate(nested)] #[validate(nested)]
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default)]
pub $field_name: Option<$ty>, pub $field_name: Option<$ty>,
)* )*
} }
@@ -114,7 +114,6 @@ macro_rules! define_project_components {
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ProjectQuery { pub struct ProjectQuery {
$( $(
#[serde(skip_serializing_if = "Option::is_none")]
pub $field_name: Option<Query<$ty>>, pub $field_name: Option<Query<$ty>>,
)* )*
} }
+2 -11
View File
@@ -210,18 +210,9 @@ pub async fn game_version_list(
.into_iter() .into_iter()
.map(|f| GameVersionQueryData { .map(|f| GameVersionQueryData {
version: f.value, version: f.value,
version_type: f version_type: f.ty.unwrap_or_default(),
.metadata
.as_ref()
.map(|metadata| metadata.type_.as_str())
.unwrap_or_default()
.to_string(),
date: f.created, date: f.created,
major: f major: f.major.unwrap_or_default(),
.metadata
.as_ref()
.map(|metadata| metadata.major)
.unwrap_or_default(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
HttpResponse::Ok().json(fields) HttpResponse::Ok().json(fields)
+6 -4
View File
@@ -11,8 +11,8 @@ use tracing::{info, warn};
use crate::database::PgPool; use crate::database::PgPool;
use crate::database::models::loader_fields::{ use crate::database::models::loader_fields::{
LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField,
QueryVersionField, VersionField, VersionField,
}; };
use crate::database::models::{ use crate::database::models::{
DBOrganizationId, DBProjectId, DBUserId, DBVersionId, LoaderFieldEnumId, DBOrganizationId, DBProjectId, DBUserId, DBVersionId, LoaderFieldEnumId,
@@ -396,7 +396,8 @@ async fn build_search_documents(
sqlx::query!( sqlx::query!(
r#" r#"
SELECT DISTINCT id, enum_id, value, ordering, created, SELECT DISTINCT id, enum_id, value, ordering, created,
metadata AS "metadata?: sqlx::types::Json<LoaderFieldEnumValueMetadata>" metadata->>'type' AS "ty?",
(metadata->>'major')::boolean AS "major?"
FROM loader_field_enum_values lfev FROM loader_field_enum_values lfev
ORDER BY enum_id, ordering, created DESC ORDER BY enum_id, ordering, created DESC
"# "#
@@ -408,7 +409,8 @@ async fn build_search_documents(
value: m.value, value: m.value,
ordering: m.ordering, ordering: m.ordering,
created: m.created, created: m.created,
metadata: m.metadata.map(|metadata| metadata.0), ty: m.ty,
major: m.major,
}) })
.try_collect() .try_collect()
.await?; .await?;
+2 -2
View File
@@ -574,7 +574,7 @@ async fn minecraft_game_version_update() {
// A couple specific checks- in the dummy data, all game versions are marked as major=false except 1.20.5 // A couple specific checks- in the dummy data, all game versions are marked as major=false except 1.20.5
let name_to_major = game_versions let name_to_major = game_versions
.iter() .iter()
.map(|x| (x.value.clone(), x.metadata.as_ref().unwrap().major)) .map(|x| (x.value.clone(), x.major.unwrap()))
.collect::<std::collections::HashMap<_, _>>(); .collect::<std::collections::HashMap<_, _>>();
for (name, major) in name_to_major { for (name, major) in name_to_major {
if name == "1.20.5" { if name == "1.20.5" {
@@ -607,7 +607,7 @@ async fn minecraft_game_version_update() {
let name_to_major = game_versions let name_to_major = game_versions
.iter() .iter()
.map(|x| (x.value.clone(), x.metadata.as_ref().unwrap().major)) .map(|x| (x.value.clone(), x.major.unwrap()))
.collect::<std::collections::HashMap<_, _>>(); .collect::<std::collections::HashMap<_, _>>();
// Confirm that the new version is there // Confirm that the new version is there
assert!(name_to_major.contains_key("1.20.6")); assert!(name_to_major.contains_key("1.20.6"));