Initial HerbAPI implementation
Rust/Axum REST API (herbapi-api) with PostgreSQL, S3/Garage, OIDC auth. Dioxus 0.7 WASM frontend (herbapi-ui) with sidebar layout and botanical reference style. 9 SQL migrations covering families, species, cultivars, suppliers, companions, images, users, API tokens.
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
// --- Pagination ---
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PaginationParams {
|
||||
pub page: Option<i64>,
|
||||
pub per_page: Option<i64>,
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
impl PaginationParams {
|
||||
pub fn limit(&self) -> i64 {
|
||||
self.per_page.unwrap_or(25).min(100)
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> i64 {
|
||||
(self.page.unwrap_or(1) - 1).max(0) * self.limit()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PaginatedResponse<T: Serialize> {
|
||||
pub data: Vec<T>,
|
||||
pub total: i64,
|
||||
pub page: i64,
|
||||
pub per_page: i64,
|
||||
}
|
||||
|
||||
// --- Families ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct Family {
|
||||
pub id: Uuid,
|
||||
pub slug: String,
|
||||
pub name_scientific: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateFamily {
|
||||
pub name_scientific: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateFamily {
|
||||
pub name_scientific: Option<String>,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
// --- Species ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct Species {
|
||||
pub id: Uuid,
|
||||
pub slug: String,
|
||||
pub family_id: Uuid,
|
||||
pub name_scientific: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub soil_moisture: Option<String>,
|
||||
pub drainage_requirement: Option<String>,
|
||||
pub organic_matter_pct: Option<f64>,
|
||||
pub nitrogen_ppm: Option<i32>,
|
||||
pub phosphorus_ppm: Option<i32>,
|
||||
pub potassium_ppm: Option<i32>,
|
||||
pub boron_ppm: Option<f64>,
|
||||
pub calcium_ppm: Option<i32>,
|
||||
pub copper_ppm: Option<f64>,
|
||||
pub iron_ppm: Option<f64>,
|
||||
pub magnesium_ppm: Option<i32>,
|
||||
pub manganese_ppm: Option<f64>,
|
||||
pub molybdenum_ppm: Option<f64>,
|
||||
pub sulfur_ppm: Option<i32>,
|
||||
pub zinc_ppm: Option<f64>,
|
||||
pub ph_min: Option<f64>,
|
||||
pub ph_max: Option<f64>,
|
||||
pub soil_texture_preference: Option<Vec<String>>,
|
||||
pub hardiness_zone_usda: Option<String>,
|
||||
pub hardiness_zone_at: Option<String>,
|
||||
pub min_temp: Option<f64>,
|
||||
pub max_temp: Option<f64>,
|
||||
pub drought_tolerance: Option<String>,
|
||||
pub water_requirement_mm_week: Option<f64>,
|
||||
pub waterlogging_tolerance: Option<bool>,
|
||||
pub salt_tolerance: Option<String>,
|
||||
pub edibility_rating: Option<i16>,
|
||||
pub food_uses: Option<String>,
|
||||
pub medicinal_uses: Option<String>,
|
||||
pub other_uses: Option<String>,
|
||||
pub native_range: Option<String>,
|
||||
pub invasiveness: Option<String>,
|
||||
pub pollination_type: Option<String>,
|
||||
pub plant_layer: Option<String>,
|
||||
pub nitrogen_fixer: Option<bool>,
|
||||
pub dynamic_accumulator: Option<bool>,
|
||||
pub dynamic_accumulator_nutrients: Option<Vec<String>>,
|
||||
pub attracts_pollinators: Option<bool>,
|
||||
pub attracts_beneficial_insects: Option<bool>,
|
||||
pub wildlife_value: Option<String>,
|
||||
pub mulch_plant: Option<bool>,
|
||||
pub ground_cover_quality: Option<String>,
|
||||
pub allelopathic: Option<bool>,
|
||||
pub guild_role: Option<Vec<String>>,
|
||||
pub succession_stage: Option<String>,
|
||||
pub heavy_metal_tolerance: Option<bool>,
|
||||
pub wikidata_qid: Option<String>,
|
||||
pub gbif_id: Option<String>,
|
||||
pub eppo_code: Option<String>,
|
||||
pub pfaf_url: Option<String>,
|
||||
pub primary_image_key: Option<String>,
|
||||
pub source_urls: Option<Vec<String>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateSpecies {
|
||||
pub family_id: Uuid,
|
||||
pub name_scientific: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub soil_moisture: Option<String>,
|
||||
pub drainage_requirement: Option<String>,
|
||||
pub ph_min: Option<f64>,
|
||||
pub ph_max: Option<f64>,
|
||||
pub soil_texture_preference: Option<Vec<String>>,
|
||||
pub hardiness_zone_usda: Option<String>,
|
||||
pub hardiness_zone_at: Option<String>,
|
||||
pub min_temp: Option<f64>,
|
||||
pub max_temp: Option<f64>,
|
||||
pub drought_tolerance: Option<String>,
|
||||
pub salt_tolerance: Option<String>,
|
||||
pub edibility_rating: Option<i16>,
|
||||
pub food_uses: Option<String>,
|
||||
pub medicinal_uses: Option<String>,
|
||||
pub other_uses: Option<String>,
|
||||
pub native_range: Option<String>,
|
||||
pub invasiveness: Option<String>,
|
||||
pub pollination_type: Option<String>,
|
||||
pub plant_layer: Option<String>,
|
||||
pub nitrogen_fixer: Option<bool>,
|
||||
pub dynamic_accumulator: Option<bool>,
|
||||
pub dynamic_accumulator_nutrients: Option<Vec<String>>,
|
||||
pub attracts_pollinators: Option<bool>,
|
||||
pub attracts_beneficial_insects: Option<bool>,
|
||||
pub wildlife_value: Option<String>,
|
||||
pub mulch_plant: Option<bool>,
|
||||
pub ground_cover_quality: Option<String>,
|
||||
pub allelopathic: Option<bool>,
|
||||
pub guild_role: Option<Vec<String>>,
|
||||
pub succession_stage: Option<String>,
|
||||
pub heavy_metal_tolerance: Option<bool>,
|
||||
pub wikidata_qid: Option<String>,
|
||||
pub gbif_id: Option<String>,
|
||||
pub eppo_code: Option<String>,
|
||||
pub pfaf_url: Option<String>,
|
||||
pub source_urls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub type UpdateSpecies = CreateSpecies;
|
||||
|
||||
// --- Cultivars ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct Cultivar {
|
||||
pub id: Uuid,
|
||||
pub slug: String,
|
||||
pub species_id: Uuid,
|
||||
pub name: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub name_scientific: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub is_organic: bool,
|
||||
pub perennial: bool,
|
||||
pub growing_time_days: Option<i32>,
|
||||
pub planting_depth_cm: Option<f64>,
|
||||
pub row_spacing_cm: Option<f64>,
|
||||
pub plant_spacing_cm: Option<f64>,
|
||||
pub days_to_germination: Option<i32>,
|
||||
pub germination_temp_c: Option<f64>,
|
||||
pub light_requirement: Option<String>,
|
||||
pub stratification_required: Option<bool>,
|
||||
pub stratification_days: Option<i32>,
|
||||
pub scarification_required: Option<bool>,
|
||||
pub seed_viability_years: Option<i32>,
|
||||
pub storage_temp_c: Option<f64>,
|
||||
pub storage_humidity: Option<String>,
|
||||
pub storage_notes: Option<String>,
|
||||
pub min_temp: Option<f64>,
|
||||
pub max_temp: Option<f64>,
|
||||
pub humidity: Option<String>,
|
||||
pub light: Option<String>,
|
||||
pub frost_tolerance: Option<String>,
|
||||
pub min_light_hours_day: Option<f64>,
|
||||
pub optimal_light_hours_day: Option<f64>,
|
||||
pub greenhouse_min_temp_c: Option<f64>,
|
||||
pub indoor_season_extension_weeks: Option<i32>,
|
||||
pub ventilation_requirement: Option<String>,
|
||||
pub heating_required: Option<bool>,
|
||||
pub indoor_sowing_months: Option<Vec<i32>>,
|
||||
pub direct_sowing_months: Option<Vec<i32>>,
|
||||
pub transplanting_months: Option<Vec<i32>>,
|
||||
pub glasshouse_months: Option<Vec<i32>>,
|
||||
pub harvesting_months: Option<Vec<i32>>,
|
||||
pub succession_planting_days: Option<i32>,
|
||||
pub planting_notes: Option<String>,
|
||||
pub propagation_methods: Option<Vec<String>>,
|
||||
pub cutting_season: Option<String>,
|
||||
pub rootstock_species_id: Option<Uuid>,
|
||||
pub years_to_first_harvest: Option<i32>,
|
||||
pub productive_lifespan_years: Option<i32>,
|
||||
pub expected_yield_kg_per_m2: Option<f64>,
|
||||
pub yield_unit: Option<String>,
|
||||
pub expected_yield_value: Option<f64>,
|
||||
pub harvest_window_days: Option<i32>,
|
||||
pub storage_method: Option<Vec<String>>,
|
||||
pub shelf_life_days: Option<i32>,
|
||||
pub cold_storage_days: Option<i32>,
|
||||
pub pollination_group: Option<String>,
|
||||
pub self_fertile: Option<bool>,
|
||||
pub rootstock_compatibility: Option<String>,
|
||||
pub wikidata_qid: Option<String>,
|
||||
pub gbif_id: Option<String>,
|
||||
pub pfaf_url: Option<String>,
|
||||
pub primary_image_key: Option<String>,
|
||||
pub source_urls: Option<Vec<String>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateCultivar {
|
||||
pub species_id: Uuid,
|
||||
pub name: String,
|
||||
pub name_en: Option<String>,
|
||||
pub name_de: Option<String>,
|
||||
pub name_scientific: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub is_organic: Option<bool>,
|
||||
pub perennial: Option<bool>,
|
||||
pub growing_time_days: Option<i32>,
|
||||
pub planting_depth_cm: Option<f64>,
|
||||
pub row_spacing_cm: Option<f64>,
|
||||
pub plant_spacing_cm: Option<f64>,
|
||||
pub days_to_germination: Option<i32>,
|
||||
pub germination_temp_c: Option<f64>,
|
||||
pub light_requirement: Option<String>,
|
||||
pub stratification_required: Option<bool>,
|
||||
pub stratification_days: Option<i32>,
|
||||
pub scarification_required: Option<bool>,
|
||||
pub seed_viability_years: Option<i32>,
|
||||
pub storage_temp_c: Option<f64>,
|
||||
pub storage_humidity: Option<String>,
|
||||
pub storage_notes: Option<String>,
|
||||
pub min_temp: Option<f64>,
|
||||
pub max_temp: Option<f64>,
|
||||
pub humidity: Option<String>,
|
||||
pub light: Option<String>,
|
||||
pub frost_tolerance: Option<String>,
|
||||
pub min_light_hours_day: Option<f64>,
|
||||
pub optimal_light_hours_day: Option<f64>,
|
||||
pub greenhouse_min_temp_c: Option<f64>,
|
||||
pub indoor_season_extension_weeks: Option<i32>,
|
||||
pub ventilation_requirement: Option<String>,
|
||||
pub heating_required: Option<bool>,
|
||||
pub indoor_sowing_months: Option<Vec<i32>>,
|
||||
pub direct_sowing_months: Option<Vec<i32>>,
|
||||
pub transplanting_months: Option<Vec<i32>>,
|
||||
pub glasshouse_months: Option<Vec<i32>>,
|
||||
pub harvesting_months: Option<Vec<i32>>,
|
||||
pub succession_planting_days: Option<i32>,
|
||||
pub planting_notes: Option<String>,
|
||||
pub propagation_methods: Option<Vec<String>>,
|
||||
pub cutting_season: Option<String>,
|
||||
pub rootstock_species_id: Option<Uuid>,
|
||||
pub years_to_first_harvest: Option<i32>,
|
||||
pub productive_lifespan_years: Option<i32>,
|
||||
pub expected_yield_kg_per_m2: Option<f64>,
|
||||
pub yield_unit: Option<String>,
|
||||
pub expected_yield_value: Option<f64>,
|
||||
pub harvest_window_days: Option<i32>,
|
||||
pub storage_method: Option<Vec<String>>,
|
||||
pub shelf_life_days: Option<i32>,
|
||||
pub cold_storage_days: Option<i32>,
|
||||
pub pollination_group: Option<String>,
|
||||
pub self_fertile: Option<bool>,
|
||||
pub rootstock_compatibility: Option<String>,
|
||||
pub wikidata_qid: Option<String>,
|
||||
pub gbif_id: Option<String>,
|
||||
pub pfaf_url: Option<String>,
|
||||
pub source_urls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub type UpdateCultivar = CreateCultivar;
|
||||
|
||||
// --- Suppliers ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct Supplier {
|
||||
pub id: Uuid,
|
||||
pub slug: String,
|
||||
pub name: String,
|
||||
pub url: Option<String>,
|
||||
pub is_organic: bool,
|
||||
pub is_demeter: bool,
|
||||
pub country: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateSupplier {
|
||||
pub name: String,
|
||||
pub url: Option<String>,
|
||||
pub is_organic: Option<bool>,
|
||||
pub is_demeter: Option<bool>,
|
||||
pub country: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
pub type UpdateSupplier = CreateSupplier;
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct CultivarSupplier {
|
||||
pub id: Uuid,
|
||||
pub cultivar_id: Uuid,
|
||||
pub supplier_id: Uuid,
|
||||
pub article_number: Option<String>,
|
||||
pub product_url: Option<String>,
|
||||
pub price_eur: Option<f64>,
|
||||
pub pack_size: Option<f64>,
|
||||
pub pack_unit: Option<String>,
|
||||
pub last_checked_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateCultivarSupplier {
|
||||
pub supplier_id: Uuid,
|
||||
pub article_number: Option<String>,
|
||||
pub product_url: Option<String>,
|
||||
pub price_eur: Option<f64>,
|
||||
pub pack_size: Option<f64>,
|
||||
pub pack_unit: Option<String>,
|
||||
}
|
||||
|
||||
// --- Companion Relationships ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct CompanionRelationship {
|
||||
pub id: Uuid,
|
||||
pub species_a_id: Uuid,
|
||||
pub species_b_id: Uuid,
|
||||
pub relationship: String,
|
||||
pub mechanism: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateCompanion {
|
||||
pub species_a_id: Uuid,
|
||||
pub species_b_id: Uuid,
|
||||
pub relationship: String,
|
||||
pub mechanism: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
}
|
||||
|
||||
// --- Images ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct Image {
|
||||
pub id: Uuid,
|
||||
pub entity_type: String,
|
||||
pub entity_id: Uuid,
|
||||
pub s3_key: String,
|
||||
pub caption: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
pub license: Option<String>,
|
||||
pub is_primary: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// --- Users ---
|
||||
|
||||
#[derive(Debug, FromRow, Serialize)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub email: String,
|
||||
pub name: Option<String>,
|
||||
pub nickname: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub provider: String,
|
||||
pub provider_id: Option<String>,
|
||||
pub admin: bool,
|
||||
pub inserted_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// --- Search ---
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SearchResult {
|
||||
pub entity_type: String,
|
||||
pub id: Uuid,
|
||||
pub slug: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub rank: f32,
|
||||
}
|
||||
Reference in New Issue
Block a user