// In V3, we switched to dynamic loader fields for a better support for more loaders, games, and potential metadata. // This file contains the legacy loader fields, which are still used by V2 projects. // They are still useful to have in several places where minecraft-java functionality is hardcoded- for example, // for fetching data from forge, maven, etc. // These fields only apply to minecraft-java, and are hardcoded to the minecraft-java game. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::json; use crate::database::redis::RedisPool; use super::{ loader_fields::{LoaderFieldEnum, LoaderFieldEnumValue, VersionField, VersionFieldValue}, DatabaseError, LoaderFieldEnumValueId, }; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct MinecraftGameVersion { pub id: LoaderFieldEnumValueId, pub version: String, #[serde(rename = "type")] pub type_: String, pub created: DateTime, pub major: bool, } impl MinecraftGameVersion { // The name under which this legacy field is stored as a LoaderField pub const FIELD_NAME: &'static str = "game_versions"; pub fn builder() -> MinecraftGameVersionBuilder<'static> { MinecraftGameVersionBuilder::default() } pub async fn list<'a, E>( exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { let game_version_enum = LoaderFieldEnum::get(Self::FIELD_NAME, exec, redis) .await? .ok_or_else(|| { DatabaseError::SchemaError("Could not find game version enum.".to_string()) })?; let game_version_enum_values = LoaderFieldEnumValue::list(game_version_enum.id, exec, redis).await?; Ok(game_version_enum_values .into_iter() .map(MinecraftGameVersion::from_enum_value) .collect()) } // TODO: remove this pub async fn list_transaction( transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, redis: &RedisPool, ) -> Result, DatabaseError> { let game_version_enum = LoaderFieldEnum::get(Self::FIELD_NAME, &mut **transaction, redis) .await? .ok_or_else(|| { DatabaseError::SchemaError("Could not find game version enum.".to_string()) })?; let game_version_enum_values = LoaderFieldEnumValue::list(game_version_enum.id, &mut **transaction, redis).await?; Ok(game_version_enum_values .into_iter() .map(MinecraftGameVersion::from_enum_value) .collect()) } // Tries to create a MinecraftGameVersion from a VersionField // Clones on success pub fn try_from_version_field( version_field: &VersionField, ) -> Result, DatabaseError> { if version_field.field_name != Self::FIELD_NAME { return Err(DatabaseError::SchemaError(format!( "Field name {} is not {}", version_field.field_name, Self::FIELD_NAME ))); } let game_versions = match version_field.clone() { VersionField { value: VersionFieldValue::ArrayEnum(_, values), .. } => values.into_iter().map(Self::from_enum_value).collect(), VersionField { value: VersionFieldValue::Enum(_, value), .. } => { vec![Self::from_enum_value(value)] } _ => { return Err(DatabaseError::SchemaError(format!( "Game version requires field value to be an enum: {:?}", version_field ))); } }; Ok(game_versions) } pub fn from_enum_value(loader_field_enum_value: LoaderFieldEnumValue) -> MinecraftGameVersion { MinecraftGameVersion { id: loader_field_enum_value.id, version: loader_field_enum_value.value, created: loader_field_enum_value.created, type_: loader_field_enum_value .metadata .get("type") .and_then(|x| x.as_str()) .map(|x| x.to_string()) .unwrap_or_default(), major: loader_field_enum_value .metadata .get("major") .and_then(|x| x.as_bool()) .unwrap_or_default(), } } } #[derive(Default)] pub struct MinecraftGameVersionBuilder<'a> { pub version: Option<&'a str>, pub version_type: Option<&'a str>, pub date: Option<&'a DateTime>, } impl<'a> MinecraftGameVersionBuilder<'a> { pub fn new() -> Self { Self::default() } /// The game version. Spaces must be replaced with '_' for it to be valid pub fn version( self, version: &'a str, ) -> Result, DatabaseError> { Ok(Self { version: Some(version), ..self }) } pub fn version_type( self, version_type: &'a str, ) -> Result, DatabaseError> { Ok(Self { version_type: Some(version_type), ..self }) } pub fn created(self, created: &'a DateTime) -> MinecraftGameVersionBuilder<'a> { Self { date: Some(created), ..self } } pub async fn insert<'b, E>( self, exec: E, redis: &RedisPool, ) -> Result where E: sqlx::Executor<'b, Database = sqlx::Postgres> + Copy, { let game_versions_enum = LoaderFieldEnum::get("game_versions", exec, redis) .await? .ok_or(DatabaseError::SchemaError( "Missing loaders field: 'game_versions'".to_string(), ))?; // Get enum id for game versions let metadata = json!({ "type": self.version_type, "major": false }); // This looks like a mess, but it *should* work // This allows game versions to be partially updated without // replacing the unspecified fields with defaults. let result = sqlx::query!( " INSERT INTO loader_field_enum_values (enum_id, value, created, metadata) VALUES ($1, $2, COALESCE($3, timezone('utc', now())), $4) ON CONFLICT (enum_id, value) DO UPDATE SET metadata = COALESCE($4, loader_field_enum_values.metadata), created = COALESCE($3, loader_field_enum_values.created) RETURNING id ", game_versions_enum.id.0, self.version, self.date.map(chrono::DateTime::naive_utc), metadata ) .fetch_one(exec) .await?; Ok(LoaderFieldEnumValueId(result.id)) } }