diff --git a/src/aggregates/mod.rs b/src/aggregates/mod.rs index 3bb94c0..0c424fc 100644 --- a/src/aggregates/mod.rs +++ b/src/aggregates/mod.rs @@ -9,6 +9,7 @@ #![cfg(feature = "ssr")] pub mod applicant; +pub mod organization; pub mod project; pub mod subscriber; diff --git a/src/aggregates/organization.rs b/src/aggregates/organization.rs new file mode 100644 index 0000000..065b811 --- /dev/null +++ b/src/aggregates/organization.rs @@ -0,0 +1,45 @@ +//! Organizations (companies). "Client" is a *status* this aggregate can +//! be in, not a separate aggregate type - `Prospect -> Client -> +//! PastClient`, with `Prospect -> PastClient` also allowed directly +//! (a prospect that never converted, archived without ever having been +//! a client). Greenfield: no content or KV bucket references this yet +//! (see the event-sourcing plan's Deferred section) - this lands +//! compiled and ready, not wired into any live content. +use super::AggregateKind; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum State { + Prospect, + Client, + PastClient, +} + +impl AggregateKind for State { + const AGGREGATE_TYPE: &'static str = "organization"; + const INITIAL_STATE: Self = State::Prospect; + + fn from_event_type(event_type: &str) -> Option { + match event_type { + "identified" => Some(State::Prospect), + "became_client" => Some(State::Client), + "churned" => Some(State::PastClient), + _ => None, + } + } + + fn event_type(self) -> &'static str { + match self { + State::Prospect => "identified", + State::Client => "became_client", + State::PastClient => "churned", + } + } + + fn allowed(self) -> &'static [Self] { + match self { + State::Prospect => &[State::Client, State::PastClient], + State::Client => &[State::PastClient], + State::PastClient => &[], + } + } +} diff --git a/src/answers.rs b/src/answers.rs index af3fe62..05babcd 100644 --- a/src/answers.rs +++ b/src/answers.rs @@ -78,7 +78,7 @@ pub async fn store_answer( // fail a submission the NATS notification event has already // recorded" policy as the KV write above. if let Some(agg_type) = crate::content::aggregate_type_for_bucket(bucket) { - use crate::aggregates::{applicant, create, project, subscriber}; + use crate::aggregates::{applicant, create, organization, project, subscriber}; let seed = match agg_type { "applicant" => create::(js, &id, responses.clone(), submitted_ms) .await @@ -89,6 +89,9 @@ pub async fn store_answer( "project" => create::(js, &id, responses.clone(), submitted_ms) .await .map(|_| ()), + "organization" => create::(js, &id, responses.clone(), submitted_ms) + .await + .map(|_| ()), _ => Ok(()), }; if let Err(e) = seed { @@ -242,7 +245,7 @@ async fn transition_by_aggregate( payload: serde_json::Value, occurred_at_ms: i64, ) -> Result<(), crate::aggregates::TransitionError> { - use crate::aggregates::{applicant, project, subscriber, transition, AggregateKind, TransitionError}; + use crate::aggregates::{applicant, organization, project, subscriber, transition, AggregateKind, TransitionError}; macro_rules! dispatch { ($state:ty) => {{ @@ -260,6 +263,7 @@ async fn transition_by_aggregate( "applicant" => dispatch!(applicant::State), "subscriber" => dispatch!(subscriber::State), "project" => dispatch!(project::State), + "organization" => dispatch!(organization::State), _ => return Err(TransitionError::UnknownAggregate), } Ok(()) diff --git a/src/content.rs b/src/content.rs index 3bd509f..ffe9e1a 100644 --- a/src/content.rs +++ b/src/content.rs @@ -345,6 +345,7 @@ pub fn aggregate_type_for_bucket(bucket: &str) -> Option<&'static str> { "applicants" => Some("applicant"), "subscribers" => Some("subscriber"), "projects" => Some("project"), + "organizations" => Some("organization"), _ => None, } } @@ -359,6 +360,7 @@ fn is_valid_transition_target(aggregate_type: &str, to: &str) -> bool { "applicant" => check::(to), "subscriber" => check::(to), "project" => check::(to), + "organization" => check::(to), _ => false, } }