From efa05b2d4492bcf0bce438d9428c8e9c534cfbde Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Sun, 15 Mar 2026 12:56:59 +0100 Subject: [PATCH] Add bilingual description fields (description_de, description_en) Species and Cultivar structs + SQL queries now support separate German and English descriptions for the language toggle feature. --- herbapi-api/src/db/cultivars.rs | 48 ++++++++++++++++++--------------- herbapi-api/src/db/models.rs | 8 ++++++ herbapi-api/src/db/species.rs | 44 ++++++++++++++++-------------- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/herbapi-api/src/db/cultivars.rs b/herbapi-api/src/db/cultivars.rs index be36441..25a02a6 100644 --- a/herbapi-api/src/db/cultivars.rs +++ b/herbapi-api/src/db/cultivars.rs @@ -118,7 +118,8 @@ pub async fn create(pool: &PgPool, req: &CreateCultivar) -> Result { sqlx::query_as::<_, Cultivar>( "INSERT INTO cultivars (id, slug, species_id, name, name_en, name_de, name_scientific, - description, is_organic, perennial, growing_time_days, planting_depth_cm, row_spacing_cm, + description, description_de, description_en, + is_organic, perennial, growing_time_days, planting_depth_cm, row_spacing_cm, plant_spacing_cm, days_to_germination, germination_temp_c, light_requirement, stratification_required, stratification_days, scarification_required, seed_viability_years, storage_temp_c, storage_humidity, storage_notes, @@ -134,14 +135,15 @@ pub async fn create(pool: &PgPool, req: &CreateCultivar) -> Result { pollination_group, self_fertile, rootstock_compatibility, wikidata_qid, gbif_id, pfaf_url, source_urls) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20, - $21,$22,$23,$24,$25,$26,$27,$28,$29::frost_tolerance,$30,$31,$32,$33,$34,$35, - $36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54, - $55,$56,$57,$58,$59,$60,$61) + $21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31::frost_tolerance,$32,$33,$34,$35,$36,$37, + $38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56, + $57,$58,$59,$60,$61,$62,$63) RETURNING *" ) .bind(id).bind(&slug).bind(req.species_id).bind(&req.name) .bind(&req.name_en).bind(&req.name_de).bind(&req.name_scientific) - .bind(&req.description).bind(req.is_organic.unwrap_or(false)).bind(req.perennial.unwrap_or(false)) + .bind(&req.description).bind(&req.description_de).bind(&req.description_en) + .bind(req.is_organic.unwrap_or(false)).bind(req.perennial.unwrap_or(false)) .bind(req.growing_time_days).bind(req.planting_depth_cm).bind(req.row_spacing_cm) .bind(req.plant_spacing_cm).bind(req.days_to_germination).bind(req.germination_temp_c) .bind(&req.light_requirement).bind(req.stratification_required).bind(req.stratification_days) @@ -180,27 +182,29 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateCultivar) -> Result( "UPDATE cultivars SET slug=$2, species_id=$3, name=$4, name_en=$5, name_de=$6, - name_scientific=$7, description=$8, is_organic=$9, perennial=$10, - growing_time_days=$11, planting_depth_cm=$12, row_spacing_cm=$13, plant_spacing_cm=$14, - days_to_germination=$15, germination_temp_c=$16, light_requirement=$17, - stratification_required=$18, stratification_days=$19, scarification_required=$20, - seed_viability_years=$21, storage_temp_c=$22, storage_humidity=$23, storage_notes=$24, - min_temp=$25, max_temp=$26, humidity=$27, light=$28, frost_tolerance=$29::frost_tolerance, - min_light_hours_day=$30, optimal_light_hours_day=$31, greenhouse_min_temp_c=$32, - indoor_season_extension_weeks=$33, ventilation_requirement=$34, heating_required=$35, - indoor_sowing_months=$36, direct_sowing_months=$37, transplanting_months=$38, - glasshouse_months=$39, harvesting_months=$40, succession_planting_days=$41, - planting_notes=$42, propagation_methods=$43, cutting_season=$44, - rootstock_species_id=$45, years_to_first_harvest=$46, productive_lifespan_years=$47, - expected_yield_kg_per_m2=$48, yield_unit=$49, expected_yield_value=$50, - harvest_window_days=$51, storage_method=$52, shelf_life_days=$53, cold_storage_days=$54, - pollination_group=$55, self_fertile=$56, rootstock_compatibility=$57, - wikidata_qid=$58, gbif_id=$59, pfaf_url=$60, source_urls=$61, updated_at=NOW() + name_scientific=$7, description=$8, description_de=$9, description_en=$10, + is_organic=$11, perennial=$12, + growing_time_days=$13, planting_depth_cm=$14, row_spacing_cm=$15, plant_spacing_cm=$16, + days_to_germination=$17, germination_temp_c=$18, light_requirement=$19, + stratification_required=$20, stratification_days=$21, scarification_required=$22, + seed_viability_years=$23, storage_temp_c=$24, storage_humidity=$25, storage_notes=$26, + min_temp=$27, max_temp=$28, humidity=$29, light=$30, frost_tolerance=$31::frost_tolerance, + min_light_hours_day=$32, optimal_light_hours_day=$33, greenhouse_min_temp_c=$34, + indoor_season_extension_weeks=$35, ventilation_requirement=$36, heating_required=$37, + indoor_sowing_months=$38, direct_sowing_months=$39, transplanting_months=$40, + glasshouse_months=$41, harvesting_months=$42, succession_planting_days=$43, + planting_notes=$44, propagation_methods=$45, cutting_season=$46, + rootstock_species_id=$47, years_to_first_harvest=$48, productive_lifespan_years=$49, + expected_yield_kg_per_m2=$50, yield_unit=$51, expected_yield_value=$52, + harvest_window_days=$53, storage_method=$54, shelf_life_days=$55, cold_storage_days=$56, + pollination_group=$57, self_fertile=$58, rootstock_compatibility=$59, + wikidata_qid=$60, gbif_id=$61, pfaf_url=$62, source_urls=$63, updated_at=NOW() WHERE id=$1 RETURNING *" ) .bind(id).bind(&slug).bind(req.species_id).bind(&req.name) .bind(&req.name_en).bind(&req.name_de).bind(&req.name_scientific) - .bind(&req.description).bind(req.is_organic.unwrap_or(false)).bind(req.perennial.unwrap_or(false)) + .bind(&req.description).bind(&req.description_de).bind(&req.description_en) + .bind(req.is_organic.unwrap_or(false)).bind(req.perennial.unwrap_or(false)) .bind(req.growing_time_days).bind(req.planting_depth_cm).bind(req.row_spacing_cm) .bind(req.plant_spacing_cm).bind(req.days_to_germination).bind(req.germination_temp_c) .bind(&req.light_requirement).bind(req.stratification_required).bind(req.stratification_days) diff --git a/herbapi-api/src/db/models.rs b/herbapi-api/src/db/models.rs index f0a1fe7..a79d4b7 100644 --- a/herbapi-api/src/db/models.rs +++ b/herbapi-api/src/db/models.rs @@ -71,6 +71,8 @@ pub struct Species { pub name_en: Option, pub name_de: Option, pub description: Option, + pub description_de: Option, + pub description_en: Option, pub soil_moisture: Option, pub drainage_requirement: Option, pub organic_matter_pct: Option, @@ -147,6 +149,8 @@ pub struct CreateSpecies { pub name_en: Option, pub name_de: Option, pub description: Option, + pub description_de: Option, + pub description_en: Option, pub soil_moisture: Option, pub drainage_requirement: Option, pub ph_min: Option, @@ -212,6 +216,8 @@ pub struct Cultivar { pub name_de: Option, pub name_scientific: Option, pub description: Option, + pub description_de: Option, + pub description_en: Option, pub is_organic: bool, pub perennial: bool, pub growing_time_days: Option, @@ -278,6 +284,8 @@ pub struct CreateCultivar { pub name_de: Option, pub name_scientific: Option, pub description: Option, + pub description_de: Option, + pub description_en: Option, pub is_organic: Option, pub perennial: Option, pub growing_time_days: Option, diff --git a/herbapi-api/src/db/species.rs b/herbapi-api/src/db/species.rs index dcdb8f1..0b5e782 100644 --- a/herbapi-api/src/db/species.rs +++ b/herbapi-api/src/db/species.rs @@ -102,6 +102,7 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result { sqlx::query_as::<_, Species>( "INSERT INTO species (id, slug, family_id, name_scientific, name_en, name_de, description, + description_de, description_en, soil_moisture, drainage_requirement, ph_min, ph_max, soil_texture_preference, hardiness_zone_usda, hardiness_zone_at, min_temp, max_temp, drought_tolerance, salt_tolerance, edibility_rating, @@ -114,15 +115,16 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result { butterfly_moth_count, caterpillar_host_count, caterpillar_specialist_count, hoverfly_count, beetle_count, bird_count, mammal_count, native_status, naturadb_tags) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16, - $17,$18,$19,$20,$21,$22,$23, - $24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36, - $37,$38,$39,$40,$41,$42,$43, - $44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18, + $19,$20,$21,$22,$23,$24,$25, + $26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38, + $39,$40,$41,$42,$43,$44,$45, + $46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58) RETURNING *" ) .bind(id).bind(&slug).bind(req.family_id).bind(&req.name_scientific) .bind(&req.name_en).bind(&req.name_de).bind(&req.description) + .bind(&req.description_de).bind(&req.description_en) .bind(&req.soil_moisture).bind(&req.drainage_requirement) .bind(req.ph_min).bind(req.ph_max).bind(&req.soil_texture_preference) .bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at) @@ -158,26 +160,28 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateSpecies) -> Result( "UPDATE species SET slug=$2, family_id=$3, name_scientific=$4, name_en=$5, name_de=$6, - description=$7, soil_moisture=$8, drainage_requirement=$9, ph_min=$10, ph_max=$11, - soil_texture_preference=$12, hardiness_zone_usda=$13, hardiness_zone_at=$14, - min_temp=$15, max_temp=$16, - drought_tolerance=$17, salt_tolerance=$18, - edibility_rating=$19, food_uses=$20, medicinal_uses=$21, other_uses=$22, - native_range=$23, invasiveness=$24, pollination_type=$25, - plant_layer=$26, nitrogen_fixer=$27, dynamic_accumulator=$28, - dynamic_accumulator_nutrients=$29, attracts_pollinators=$30, attracts_beneficial_insects=$31, - wildlife_value=$32, mulch_plant=$33, ground_cover_quality=$34, allelopathic=$35, - guild_role=$36, succession_stage=$37, heavy_metal_tolerance=$38, - wikidata_qid=$39, gbif_id=$40, eppo_code=$41, pfaf_url=$42, source_urls=$43, - nectar_value=$44, pollen_value=$45, wild_bee_count=$46, wild_bee_specialist_count=$47, - butterfly_moth_count=$48, caterpillar_host_count=$49, caterpillar_specialist_count=$50, - hoverfly_count=$51, beetle_count=$52, bird_count=$53, mammal_count=$54, - native_status=$55, naturadb_tags=$56, + description=$7, description_de=$8, description_en=$9, + soil_moisture=$10, drainage_requirement=$11, ph_min=$12, ph_max=$13, + soil_texture_preference=$14, hardiness_zone_usda=$15, hardiness_zone_at=$16, + min_temp=$17, max_temp=$18, + drought_tolerance=$19, salt_tolerance=$20, + edibility_rating=$21, food_uses=$22, medicinal_uses=$23, other_uses=$24, + native_range=$25, invasiveness=$26, pollination_type=$27, + plant_layer=$28, nitrogen_fixer=$29, dynamic_accumulator=$30, + dynamic_accumulator_nutrients=$31, attracts_pollinators=$32, attracts_beneficial_insects=$33, + wildlife_value=$34, mulch_plant=$35, ground_cover_quality=$36, allelopathic=$37, + guild_role=$38, succession_stage=$39, heavy_metal_tolerance=$40, + wikidata_qid=$41, gbif_id=$42, eppo_code=$43, pfaf_url=$44, source_urls=$45, + nectar_value=$46, pollen_value=$47, wild_bee_count=$48, wild_bee_specialist_count=$49, + butterfly_moth_count=$50, caterpillar_host_count=$51, caterpillar_specialist_count=$52, + hoverfly_count=$53, beetle_count=$54, bird_count=$55, mammal_count=$56, + native_status=$57, naturadb_tags=$58, updated_at=NOW() WHERE id=$1 RETURNING *" ) .bind(id).bind(&slug).bind(req.family_id).bind(&req.name_scientific) .bind(&req.name_en).bind(&req.name_de).bind(&req.description) + .bind(&req.description_de).bind(&req.description_en) .bind(&req.soil_moisture).bind(&req.drainage_requirement) .bind(req.ph_min).bind(req.ph_max).bind(&req.soil_texture_preference) .bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at)