Skip to content

Commit

Permalink
feat: adjust enroll logic and output for the new subscription plans
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianbenavides committed Nov 25, 2024
1 parent b496621 commit d7c9f1e
Show file tree
Hide file tree
Showing 27 changed files with 511 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ impl SpacesRepository for SpacesSqlxDatabase {
.bind(&subscription.name)
.bind(subscription.is_free_trial)
.bind(&subscription.marketplace)
.bind(start_date.map(|d| d.unix_timestamp()))
.bind(end_date.map(|d| d.unix_timestamp()));
.bind(start_date.map(|d| d.into_inner().unix_timestamp()))
.bind(end_date.map(|d| d.into_inner().unix_timestamp()));
query.execute(&mut *transaction).await.void()?;
}
// remove the subscription
Expand Down Expand Up @@ -307,10 +307,12 @@ impl SubscriptionRow {
self.marketplace.to_option(),
self.start_date
.to_option()
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok()),
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
.and_then(|t| t.try_into().ok()),
self.end_date
.to_option()
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok()),
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
.and_then(|t| t.try_into().ok()),
)
}
}
Expand Down Expand Up @@ -346,8 +348,8 @@ mod test {
"premium".to_string(),
false,
Some("aws".to_string()),
Some(OffsetDateTime::now_utc()),
Some(OffsetDateTime::now_utc().add(2.days())),
Some(OffsetDateTime::now_utc().try_into().unwrap()),
Some(OffsetDateTime::now_utc().add(2.days()).try_into().unwrap()),
)),
};

Expand Down
119 changes: 83 additions & 36 deletions implementations/rust/ockam/ockam_api/src/cloud/space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use ockam_node::Context;
use crate::cloud::email_address::EmailAddress;
use crate::cloud::project::models::AdminInfo;
use crate::cloud::project::{Project, ProjectsOrchestratorApi};
use crate::cloud::subscription::Subscription;
use crate::cloud::subscription::{Subscription, SUBSCRIPTION_PAGE};
use crate::cloud::{ControllerClient, HasSecureClient};
use crate::colors::{color_primary, color_uri, color_warn};
use crate::fmt_log;
Expand Down Expand Up @@ -39,8 +39,11 @@ impl Space {
self.name.clone()
}

pub fn has_subscription(&self) -> bool {
self.subscription.is_some()
pub fn has_valid_subscription(&self) -> bool {
self.subscription
.as_ref()
.map(|s| s.is_valid())
.unwrap_or(false)
}

pub fn is_in_free_trial_subscription(&self) -> bool {
Expand All @@ -52,46 +55,40 @@ impl Space {
.unwrap_or_default()
}

pub fn subscription_status_message(&self, space_is_new: bool) -> crate::Result<String> {
pub fn subscription_status_message(&self) -> crate::Result<String> {
let mut f = String::new();
if let Some(subscription) = &self.subscription {
let trial_text = if subscription.is_free_trial {
"Trial of the "
} else {
""
};
writeln!(
f,
"{}",
fmt_log!(
"This Space has a {} Subscription attached to it.",
color_primary(&subscription.name)
"This Space has a {}{} Subscription attached to it.",
trial_text,
subscription.name_colored(),
)
)?;
if let (Some(start_date), Some(end_date)) =
(&subscription.start_date(), &subscription.end_date())
{
writeln!(
f,
"{}",
fmt_log!(
"Your trial started on {} and will end in {}.",
color_primary(start_date.format_human()?),
color_primary(end_date.diff_human(start_date))
)
)?;
}
if subscription.is_free_trial {
if space_is_new {
writeln!(f)?;
writeln!(f, "{}", fmt_log!("As a courtesy, we created a temporary Space for you, so you can continue to build.\n"))?;
writeln!(
f,
"{}",
fmt_log!(
"Please subscribe to an Ockam plan within two weeks {}",
color_uri("https://www.ockam.io/pricing")
)
)?;
writeln!(f, "{}", fmt_log!("{}", color_warn("If you don't subscribe in that time, your Space and all associated Projects will be permanently deleted.")))?;
} else if let (Some(start_date), Some(end_date)) =
(&subscription.start_date(), &subscription.end_date())
{
writeln!(f)?;
writeln!(
f,
"{}",
fmt_log!(
"Your free trial started on {} and will end on {}.\n",
start_date,
end_date
)
)?;
writeln!(f, "{}", fmt_log!("Please subscribe to an Ockam plan before the trial ends to avoid any service interruptions {}", color_uri("https://www.ockam.io/pricing")))?;
writeln!(f, "{}", fmt_log!("{}", color_warn("If you don't subscribe in that time, your Space and all associated Projects will be permanently deleted.")))?;
}
writeln!(f)?;
writeln!(f, "{}", fmt_log!("Please go to {} and subscribe before the trial ends to avoid any service interruptions.", color_uri(SUBSCRIPTION_PAGE)))?;
writeln!(f, "{}", fmt_log!("{}", color_warn("If you don't subscribe in that time, your Space and all associated Projects will be permanently deleted.")))?;
}
} else {
writeln!(
Expand Down Expand Up @@ -362,10 +359,10 @@ impl ControllerClient {

#[cfg(test)]
pub mod tests {
use quickcheck::{quickcheck, Arbitrary, Gen, TestResult};

use crate::cloud::space::CreateSpace;
use crate::schema::tests::validate_with_schema;
use quickcheck::{quickcheck, Arbitrary, Gen, TestResult};
use time::OffsetDateTime;

use super::*;

Expand Down Expand Up @@ -402,4 +399,54 @@ pub mod tests {
}
}
}

#[test]
fn valid_subscription_check() {
let mut g = Gen::new(100);
let mut space = Space::arbitrary(&mut g);

// No subscription
space.subscription = None;
assert!(!space.has_valid_subscription());

// Paid subscription
let mut sub = Subscription::arbitrary(&mut g);
sub.is_free_trial = false;
space.subscription = Some(sub.clone());
assert!(space.has_valid_subscription());

// Trial subscription with no dates
let mut sub = Subscription::arbitrary(&mut g);
sub.is_free_trial = true;
sub.start_date = None;
sub.end_date = None;
space.subscription = Some(sub.clone());
assert!(!space.has_valid_subscription());

// Trial subscription with date values
sub.start_date = Some(
(OffsetDateTime::now_utc() - time::Duration::days(1))
.try_into()
.unwrap(),
);
sub.end_date = None;
space.subscription = Some(sub.clone());
assert!(!space.has_valid_subscription());

sub.end_date = Some(
(OffsetDateTime::now_utc() - time::Duration::hours(1))
.try_into()
.unwrap(),
);
space.subscription = Some(sub.clone());
assert!(!space.has_valid_subscription());

sub.end_date = Some(
(OffsetDateTime::now_utc() + time::Duration::hours(1))
.try_into()
.unwrap(),
);
space.subscription = Some(sub.clone());
assert!(space.has_valid_subscription());
}
}
109 changes: 62 additions & 47 deletions implementations/rust/ockam/ockam_api/src/cloud/subscription.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::cloud::{ControllerClient, HasSecureClient};
use crate::output::Output;
use colorful::{Colorful, RGB};
use minicbor::{CborLen, Decode, Encode};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Write};
use time::format_description::well_known::Iso8601;
use time::OffsetDateTime;

use crate::colors::color_primary;
use crate::date::parse_date;
use crate::colors::{color_primary, OckamColor};
use crate::date::UtcDateTime;
use crate::terminal::fmt;
use ockam_core::api::{Error, Reply, Request, Status};
use ockam_core::{self, async_trait, Result};
Expand All @@ -16,6 +15,8 @@ use ockam_node::Context;
const TARGET: &str = "ockam_api::cloud::subscription";
const API_SERVICE: &str = "subscriptions";

pub const SUBSCRIPTION_PAGE: &str = "https://orchestrator.ockam.io";

#[derive(Encode, Decode, CborLen, Debug)]
#[cfg_attr(test, derive(Clone))]
#[rustfmt::skip]
Expand Down Expand Up @@ -54,86 +55,100 @@ impl ActivateSubscription {
}
}

#[derive(Encode, Decode, CborLen, Serialize, Deserialize, Clone, Debug, Eq)]
#[derive(Encode, Decode, CborLen, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[cbor(map)]
pub struct Subscription {
#[n(1)]
pub name: String,
pub name: String, // TODO: Use enum for known values?
#[n(2)]
pub is_free_trial: bool,
#[n(3)]
pub marketplace: Option<String>,
#[n(4)]
start_date: Option<String>,
pub(crate) start_date: Option<UtcDateTime>,
#[n(5)]
end_date: Option<String>,
}

impl PartialEq for Subscription {
fn eq(&self, other: &Self) -> bool {
// Compare the dates using as unix timestamps, using a tolerance of 1 second
let start_date_eq = match (self.start_date(), other.start_date()) {
(Some(start_date), Some(other_start_date)) => {
let start_date = start_date.unix_timestamp();
let other_start_date = other_start_date.unix_timestamp();
(start_date - other_start_date).abs() <= 1
}
(None, None) => true,
_ => false,
};
self.name == other.name
&& self.is_free_trial == other.is_free_trial
&& self.marketplace == other.marketplace
&& start_date_eq
}
pub(crate) end_date: Option<UtcDateTime>,
}

impl Subscription {
pub fn new(
name: String,
is_free_trial: bool,
marketplace: Option<String>,
start_date: Option<OffsetDateTime>,
end_date: Option<OffsetDateTime>,
start_date: Option<UtcDateTime>,
end_date: Option<UtcDateTime>,
) -> Self {
Self {
name,
is_free_trial,
marketplace,
start_date: start_date.and_then(|date| date.format(&Iso8601::DEFAULT).ok()),
end_date: end_date.and_then(|date| date.format(&Iso8601::DEFAULT).ok()),
start_date,
end_date,
}
}

pub fn end_date(&self) -> Option<UtcDateTime> {
self.end_date.clone()
}

pub fn start_date(&self) -> Option<UtcDateTime> {
self.start_date.clone()
}

/// A subscription is valid if:
/// - is in a trial period and end date is in the future
/// - a plan is not in trial status
pub fn is_valid(&self) -> bool {
if self.is_free_trial {
self.end_date()
.map(|end_date| end_date.is_in_the_future())
.unwrap_or(false)
} else {
true
}
}

pub fn end_date(&self) -> Option<OffsetDateTime> {
self.end_date
.as_ref()
.and_then(|date| parse_date(date).ok())
pub fn grace_period_end_date(&self) -> crate::Result<Option<UtcDateTime>> {
if !self.is_free_trial {
return Ok(None);
}
match self.end_date.as_ref() {
Some(end_date) => {
let grace_period = time::Duration::days(3);
let end_date = end_date.clone().into_inner() + grace_period;
Ok(Some(UtcDateTime::new(end_date)?))
}
None => Ok(None),
}
}

pub fn start_date(&self) -> Option<OffsetDateTime> {
self.start_date
.as_ref()
.and_then(|date| parse_date(date).ok())
pub fn name_colored(&self) -> String {
let color = match self.name.to_lowercase().as_str() {
"gold" => RGB::new(255, 215, 0),
"silver" => RGB::new(230, 232, 250),
"bronze" => RGB::new(140, 120, 83),
_ => OckamColor::PrimaryResource.color(),
};
self.name.clone().color(color).to_string()
}
}

impl Display for Subscription {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Subscription: {}", color_primary(&self.name))?;
if self.is_free_trial {
writeln!(f, " (free trial)")?;
let trial_text = if self.is_free_trial {
"Trial of the "
} else {
writeln!(f)?;
}
""
};
writeln!(f, "{}{} Subscription", trial_text, self.name_colored())?;

if let (Some(start_date), Some(end_date)) = (self.start_date(), self.end_date()) {
writeln!(
f,
"{}Started at {}, expires at {}",
"{}Started on {}, expires in {}",
fmt::INDENTATION,
color_primary(start_date.to_string()),
color_primary(end_date.to_string()),
color_primary(start_date.format_human()?),
color_primary(end_date.diff_human(&start_date)),
)?;
}

Expand Down
Loading

0 comments on commit d7c9f1e

Please sign in to comment.