Add Gear entity to track brewing equipment (grinders and brewers) with complete domain layer implementation. Database changes: - migrations/0006_add_gear.sql: Create gear table with category CHECK constraint and indexes - migrations/0007_update_timeline_for_gear.sql: Document 'gear' as valid timeline entity type Domain layer: - Add GearId typed ID wrapper - Create domain/gear.rs with: - GearCategory enum (Grinder/Brewer) with string conversion methods - Gear entity with make, model, notes fields - NewGear and UpdateGear DTOs - GearFilter for category-based filtering - GearSortKey with Make (default), Model, Category, CreatedAt options - Add GearRepository trait to domain/repositories.rs with standard CRUD operations - Register gear module in domain/mod.rs This follows the same architectural pattern as the Bag entity.
119 lines
2.8 KiB
Rust
119 lines
2.8 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::ids::GearId;
|
|
use super::listing::{SortDirection, SortKey};
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum GearCategory {
|
|
Grinder,
|
|
Brewer,
|
|
}
|
|
|
|
impl GearCategory {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
GearCategory::Grinder => "grinder",
|
|
GearCategory::Brewer => "brewer",
|
|
}
|
|
}
|
|
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
match s.to_lowercase().as_str() {
|
|
"grinder" => Some(GearCategory::Grinder),
|
|
"brewer" => Some(GearCategory::Brewer),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn display_label(&self) -> &'static str {
|
|
match self {
|
|
GearCategory::Grinder => "Grinder",
|
|
GearCategory::Brewer => "Brewer",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Gear {
|
|
pub id: GearId,
|
|
pub category: GearCategory,
|
|
pub make: String,
|
|
pub model: String,
|
|
pub notes: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct NewGear {
|
|
pub category: GearCategory,
|
|
pub make: String,
|
|
pub model: String,
|
|
pub notes: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UpdateGear {
|
|
pub make: Option<String>,
|
|
pub model: Option<String>,
|
|
pub notes: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct GearFilter {
|
|
pub category: Option<GearCategory>,
|
|
}
|
|
|
|
impl GearFilter {
|
|
pub fn all() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn for_category(category: GearCategory) -> Self {
|
|
Self {
|
|
category: Some(category),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
|
pub enum GearSortKey {
|
|
Make,
|
|
Model,
|
|
Category,
|
|
CreatedAt,
|
|
}
|
|
|
|
impl SortKey for GearSortKey {
|
|
fn default() -> Self {
|
|
GearSortKey::Make
|
|
}
|
|
|
|
fn from_query(value: &str) -> Option<Self> {
|
|
match value {
|
|
"make" => Some(GearSortKey::Make),
|
|
"model" => Some(GearSortKey::Model),
|
|
"category" => Some(GearSortKey::Category),
|
|
"created-at" => Some(GearSortKey::CreatedAt),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn query_value(self) -> &'static str {
|
|
match self {
|
|
GearSortKey::Make => "make",
|
|
GearSortKey::Model => "model",
|
|
GearSortKey::Category => "category",
|
|
GearSortKey::CreatedAt => "created-at",
|
|
}
|
|
}
|
|
|
|
fn default_direction(self) -> SortDirection {
|
|
match self {
|
|
GearSortKey::Make | GearSortKey::Model | GearSortKey::Category => SortDirection::Asc,
|
|
GearSortKey::CreatedAt => SortDirection::Desc,
|
|
}
|
|
}
|
|
}
|