Add Organization aggregate (client-as-status), wired but inert
Deploy / deploy (push) Successful in 34s
Deploy / deploy (push) Successful in 34s
Prospect -> Client -> PastClient (Prospect -> PastClient allowed directly too - a prospect that never converted). "Client" is a status on Organization, not a separate aggregate, per the event-sourcing plan's design decision. Wired into the same dispatch points as the other three aggregates (content.rs's bucket map, answers.rs's create/transition dispatch) so it's ready the moment content references an "organizations" bucket - nothing does yet, so this lands compiled and tested but with zero production surface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
755b796ad1
commit
aa18c69be4
@@ -9,6 +9,7 @@
|
|||||||
#![cfg(feature = "ssr")]
|
#![cfg(feature = "ssr")]
|
||||||
|
|
||||||
pub mod applicant;
|
pub mod applicant;
|
||||||
|
pub mod organization;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod subscriber;
|
pub mod subscriber;
|
||||||
|
|
||||||
|
|||||||
@@ -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<Self> {
|
||||||
|
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 => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-2
@@ -78,7 +78,7 @@ pub async fn store_answer(
|
|||||||
// fail a submission the NATS notification event has already
|
// fail a submission the NATS notification event has already
|
||||||
// recorded" policy as the KV write above.
|
// recorded" policy as the KV write above.
|
||||||
if let Some(agg_type) = crate::content::aggregate_type_for_bucket(bucket) {
|
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 {
|
let seed = match agg_type {
|
||||||
"applicant" => create::<applicant::State>(js, &id, responses.clone(), submitted_ms)
|
"applicant" => create::<applicant::State>(js, &id, responses.clone(), submitted_ms)
|
||||||
.await
|
.await
|
||||||
@@ -89,6 +89,9 @@ pub async fn store_answer(
|
|||||||
"project" => create::<project::State>(js, &id, responses.clone(), submitted_ms)
|
"project" => create::<project::State>(js, &id, responses.clone(), submitted_ms)
|
||||||
.await
|
.await
|
||||||
.map(|_| ()),
|
.map(|_| ()),
|
||||||
|
"organization" => create::<organization::State>(js, &id, responses.clone(), submitted_ms)
|
||||||
|
.await
|
||||||
|
.map(|_| ()),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
if let Err(e) = seed {
|
if let Err(e) = seed {
|
||||||
@@ -242,7 +245,7 @@ async fn transition_by_aggregate(
|
|||||||
payload: serde_json::Value,
|
payload: serde_json::Value,
|
||||||
occurred_at_ms: i64,
|
occurred_at_ms: i64,
|
||||||
) -> Result<(), crate::aggregates::TransitionError> {
|
) -> 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 {
|
macro_rules! dispatch {
|
||||||
($state:ty) => {{
|
($state:ty) => {{
|
||||||
@@ -260,6 +263,7 @@ async fn transition_by_aggregate(
|
|||||||
"applicant" => dispatch!(applicant::State),
|
"applicant" => dispatch!(applicant::State),
|
||||||
"subscriber" => dispatch!(subscriber::State),
|
"subscriber" => dispatch!(subscriber::State),
|
||||||
"project" => dispatch!(project::State),
|
"project" => dispatch!(project::State),
|
||||||
|
"organization" => dispatch!(organization::State),
|
||||||
_ => return Err(TransitionError::UnknownAggregate),
|
_ => return Err(TransitionError::UnknownAggregate),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ pub fn aggregate_type_for_bucket(bucket: &str) -> Option<&'static str> {
|
|||||||
"applicants" => Some("applicant"),
|
"applicants" => Some("applicant"),
|
||||||
"subscribers" => Some("subscriber"),
|
"subscribers" => Some("subscriber"),
|
||||||
"projects" => Some("project"),
|
"projects" => Some("project"),
|
||||||
|
"organizations" => Some("organization"),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,6 +360,7 @@ fn is_valid_transition_target(aggregate_type: &str, to: &str) -> bool {
|
|||||||
"applicant" => check::<crate::aggregates::applicant::State>(to),
|
"applicant" => check::<crate::aggregates::applicant::State>(to),
|
||||||
"subscriber" => check::<crate::aggregates::subscriber::State>(to),
|
"subscriber" => check::<crate::aggregates::subscriber::State>(to),
|
||||||
"project" => check::<crate::aggregates::project::State>(to),
|
"project" => check::<crate::aggregates::project::State>(to),
|
||||||
|
"organization" => check::<crate::aggregates::organization::State>(to),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user