4.2 KiB · text History 504ebaf
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

/// What's stored in `rankings.rank`. The domain type is [`Rank`], which
/// carries `Obsessed`'s date; this is just its tag.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(16))")]
#[serde(rename_all = "lowercase")]
pub enum RankKind {
    #[sea_orm(string_value = "obsessed")]
    Obsessed,
    #[sea_orm(string_value = "always")]
    Always,
    #[sea_orm(string_value = "positive")]
    Positive,
    #[sea_orm(string_value = "neutral")]
    Neutral,
    #[sea_orm(string_value = "never")]
    Never,
}

impl RankKind {
    /// Strongest first — the order the rank buttons appear in.
    pub const ALL: [RankKind; 5] =
        [RankKind::Obsessed, RankKind::Always, RankKind::Positive, RankKind::Neutral, RankKind::Never];

    pub fn as_str(self) -> &'static str {
        match self {
            RankKind::Obsessed => "obsessed",
            RankKind::Always => "always",
            RankKind::Positive => "positive",
            RankKind::Neutral => "neutral",
            RankKind::Never => "never",
        }
    }

    pub fn parse(s: &str) -> Option<RankKind> {
        RankKind::ALL.into_iter().find(|r| r.as_str() == s)
    }
}

/// How one person feels about one restaurant. Unranked counts as `Neutral`.
///
/// Serializes as its kind's name ("obsessed", "always", …), which is what
/// templates and the map compare against; read `since` with [`Rank::since`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Rank {
    Obsessed { since: Date },
    Always,
    Positive,
    Neutral,
    Never,
}

impl Rank {
    pub fn kind(self) -> RankKind {
        match self {
            Rank::Obsessed { .. } => RankKind::Obsessed,
            Rank::Always => RankKind::Always,
            Rank::Positive => RankKind::Positive,
            Rank::Neutral => RankKind::Neutral,
            Rank::Never => RankKind::Never,
        }
    }

    pub fn as_str(self) -> &'static str {
        self.kind().as_str()
    }

    pub fn since(self) -> Option<Date> {
        match self {
            Rank::Obsessed { since } => Some(since),
            _ => None,
        }
    }

    /// Rebuild from the two columns. `fallback` dates an obsession whose
    /// `obsessed_since` is somehow missing.
    pub fn from_parts(kind: RankKind, since: Option<Date>, fallback: Date) -> Rank {
        match kind {
            RankKind::Obsessed => Rank::Obsessed { since: since.unwrap_or(fallback) },
            RankKind::Always => Rank::Always,
            RankKind::Positive => Rank::Positive,
            RankKind::Neutral => Rank::Neutral,
            RankKind::Never => Rank::Never,
        }
    }
}

impl Serialize for Rank {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(self.as_str())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "rankings")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub user_id: Uuid,
    #[sea_orm(primary_key, auto_increment = false)]
    pub restaurant_id: Uuid,
    pub rank: RankKind,
    /// Set exactly when `rank` is `Obsessed`.
    pub obsessed_since: Option<Date>,
    pub updated_at: DateTimeWithTimeZone,
}

impl Model {
    pub fn rank(&self) -> Rank {
        Rank::from_parts(self.rank, self.obsessed_since, self.updated_at.date_naive())
    }
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::restaurants::Entity",
        from = "Column::RestaurantId",
        to = "super::restaurants::Column::Id",
        on_delete = "Cascade"
    )]
    Restaurants,
    #[sea_orm(
        belongs_to = "super::users::Entity",
        from = "Column::UserId",
        to = "super::users::Column::Id",
        on_delete = "Cascade"
    )]
    Users,
}

impl Related<super::restaurants::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Restaurants.def()
    }
}

impl Related<super::users::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Users.def()
    }
}

impl ActiveModelBehavior for ActiveModel {}