mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
Merge pull request #13 from storytold/premium-plans
Premium plan definitions
This commit is contained in:
7 files changed
+559
-209
No files matched your search
@@ -76,4 +76,4 @@ STRIPE_CHECKOUT_SUCCESS_URL=http://localhost:12345/stripe/checkout/success
|
||||
STRIPE_CHECKOUT_CANCEL_URL=http://localhost:12345/stripe/checkout/cancel
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_51KZvWXEU5se17Mekx6e0gw32ovDF78k2eeaGctLIs8oQVxN6DerEp1BDqh6gLMYKli4VsTZoVGHoxq5RmwKMlxtP00r59UEEad
|
||||
STRIPE_SECRET_KEY=sk_test_51KZvWXEU5se17Mek11ZdnWr4frnyw7tWOPuvl3pRzL290zvkT6KqcbGUl3fcvc3hA8oH6lWXz2kGWXXUlNcCuDxB003uVT9JX0
|
||||
STRIPE_SECRET_WEBHOOK_SIGNING_KEY=whsec_40b60bd75768052532b63dbeefa7a1b163e182c3e657fb27fa906ef49a2cd59f
|
||||
STRIPE_SECRET_WEBHOOK_SIGNING_KEY=whsec_40b60bd75768052532b63dbeefa7a1b163e182c3e657fb27fa906ef49a2cd59f
|
||||
Generated
+64
@@ -2854,6 +2854,32 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||
dependencies = [
|
||||
"num-bigint 0.1.44",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rand 0.4.6",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.3.2"
|
||||
@@ -2894,6 +2920,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
@@ -2915,6 +2951,18 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
|
||||
dependencies = [
|
||||
"num-bigint 0.1.44",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
@@ -3633,6 +3681,12 @@ dependencies = [
|
||||
"url 2.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -3998,6 +4052,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "speculoos"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bcff6a72e2ddfb09ce0064568857e70d3cf6f4342244ebb1f84fa6f89b16c26"
|
||||
dependencies = [
|
||||
"num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
@@ -4252,6 +4315,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha2 0.9.5",
|
||||
"speculoos",
|
||||
"sqlx",
|
||||
"storage_buckets_common",
|
||||
"subprocess",
|
||||
|
||||
@@ -143,3 +143,6 @@ pulldown-cmark = "0.8.0"
|
||||
[build-dependencies]
|
||||
# NB: Only using diesel for migrations support
|
||||
diesel = { version = "1.4.6", features = ["mysql"] }
|
||||
|
||||
[dev-dependencies]
|
||||
speculoos = "0.9.0" # Fluent assertions
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod plan;
|
||||
pub mod plan_builder;
|
||||
pub mod plan_category;
|
||||
pub mod plan_list;
|
||||
@@ -1,14 +1,24 @@
|
||||
use chrono::Duration;
|
||||
use crate::configs::plans::plan_builder::PlanBuilder;
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
|
||||
const TTS_DEFAULT_PRIORITY_LEVEL : u8 = 0;
|
||||
const TTS_DEFAULT_DURATION_SECONDS : i64 = 12;
|
||||
|
||||
const VC_DEFAULT_MAX_CONCURRENT_MODELS : u32 = 5;
|
||||
|
||||
const VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
const VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
|
||||
const W2L_DEFAULT_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
|
||||
|
||||
/// A Plan is either a free or premium plan.
|
||||
/// Each plan corresponds to a certain level of service.
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Plan {
|
||||
/// Name of the plan
|
||||
plan_name: String,
|
||||
/// Name of the plan in URL-friendly "slug" form (human readable, no spaces, underscores),
|
||||
/// eg. "plan_1", "en_basic", etc.
|
||||
plan_slug: String,
|
||||
|
||||
/// Whether the plan is free, paid, or loyalty-based
|
||||
plan_category: PlanCategory,
|
||||
@@ -29,6 +39,93 @@ pub struct Plan {
|
||||
|
||||
// ========== Features for TTS ==========
|
||||
|
||||
tts_base_priority_level: u8,
|
||||
tts_max_duration: Duration,
|
||||
tts_can_generate_mp3: bool, // TODO: This feature doesn't exist yet.
|
||||
tts_can_upload_private_models: bool,
|
||||
tts_can_share_private_models: bool,
|
||||
|
||||
// ========== Features for Voice Conversion ==========
|
||||
|
||||
vc_max_concurrent_models: u32,
|
||||
|
||||
vc_pre_recorded_time_limit: Duration,
|
||||
vc_pre_recorded_time_is_unlimited: bool,
|
||||
|
||||
vc_real_time_time_limit: Duration,
|
||||
vc_real_time_time_is_unlimited: bool,
|
||||
|
||||
// ========== Features for W2L ==========
|
||||
|
||||
w2l_max_duration: Duration,
|
||||
w2l_can_turn_off_watermark: bool,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
pub fn from_builder(builder: &PlanBuilder) -> Self {
|
||||
Self {
|
||||
plan_slug: builder.plan_slug.clone(),
|
||||
plan_category: builder.plan_category,
|
||||
cost_per_month_dollars: builder.cost_per_month_dollars.clone(),
|
||||
stripe_product_id: builder.stripe_product_id.clone(),
|
||||
stripe_price_id: builder.stripe_price_id.clone(),
|
||||
is_development_plan: builder.is_development_plan,
|
||||
tts_base_priority_level: builder.tts_base_priority_level,
|
||||
tts_max_duration: builder.tts_max_duration,
|
||||
tts_can_generate_mp3: builder.tts_can_generate_mp3,
|
||||
tts_can_upload_private_models: builder.tts_can_upload_private_models,
|
||||
tts_can_share_private_models: builder.tts_can_share_private_models,
|
||||
vc_max_concurrent_models: builder.vc_max_concurrent_models,
|
||||
vc_pre_recorded_time_limit: builder.vc_pre_recorded_time_limit,
|
||||
vc_pre_recorded_time_is_unlimited: builder.vc_pre_recorded_time_is_unlimited,
|
||||
vc_real_time_time_limit: builder.vc_real_time_time_limit,
|
||||
vc_real_time_time_is_unlimited: builder.vc_real_time_time_is_unlimited,
|
||||
w2l_max_duration: builder.w2l_max_duration,
|
||||
w2l_can_turn_off_watermark: builder.w2l_can_turn_off_watermark,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plan_slug(&self) -> &str {
|
||||
&self.plan_slug
|
||||
}
|
||||
|
||||
pub fn plan_category(&self) -> PlanCategory {
|
||||
self.plan_category
|
||||
}
|
||||
|
||||
pub fn stripe_product_id(&self) -> Option<&str> {
|
||||
self.stripe_product_id.as_deref()
|
||||
}
|
||||
|
||||
pub fn stripe_price_id(&self) -> Option<&str> {
|
||||
self.stripe_price_id.as_deref()
|
||||
}
|
||||
|
||||
pub fn is_development_plan(&self) -> bool {
|
||||
self.is_development_plan
|
||||
}
|
||||
|
||||
pub fn tts_base_priority_level(&self) -> u8 {
|
||||
self.tts_base_priority_level
|
||||
}
|
||||
|
||||
pub fn tts_max_duration(&self) -> Duration {
|
||||
self.tts_max_duration
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder pattern for Plans.
|
||||
#[derive(Clone)]
|
||||
pub struct PlanBuilder {
|
||||
plan_slug: String,
|
||||
plan_category: PlanCategory,
|
||||
cost_per_month_dollars: Option<u32>,
|
||||
stripe_product_id: Option<String>,
|
||||
stripe_price_id: Option<String>,
|
||||
is_development_plan: bool,
|
||||
|
||||
// ========== Features for TTS ==========
|
||||
|
||||
tts_base_priority_level: u8,
|
||||
tts_max_duration: Duration,
|
||||
tts_can_generate_mp3: bool,
|
||||
@@ -51,27 +148,131 @@ pub struct Plan {
|
||||
w2l_can_turn_off_watermark: bool,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
pub fn from_builder(builder: &PlanBuilder) -> Self {
|
||||
Self {
|
||||
plan_name: builder.plan_name.clone(),
|
||||
plan_category: builder.plan_category,
|
||||
cost_per_month_dollars: builder.cost_per_month_dollars.clone(),
|
||||
stripe_product_id: builder.stripe_product_id.clone(),
|
||||
stripe_price_id: builder.stripe_price_id.clone(),
|
||||
is_development_plan: builder.is_development_plan,
|
||||
tts_base_priority_level: builder.tts_base_priority_level,
|
||||
tts_max_duration: builder.tts_max_duration,
|
||||
tts_can_generate_mp3: builder.tts_can_generate_mp3,
|
||||
tts_can_upload_private_models: builder.tts_can_upload_private_models,
|
||||
tts_can_share_private_models: builder.tts_can_share_private_models,
|
||||
vc_max_concurrent_models: builder.vc_max_concurrent_models,
|
||||
vc_pre_recorded_time_limit: builder.vc_pre_recorded_time_limit,
|
||||
vc_pre_recorded_time_is_unlimited: builder.vc_pre_recorded_time_is_unlimited,
|
||||
vc_real_time_time_limit: builder.vc_real_time_time_limit,
|
||||
vc_real_time_time_is_unlimited: builder.vc_real_time_time_is_unlimited,
|
||||
w2l_max_duration: builder.w2l_max_duration,
|
||||
w2l_can_turn_off_watermark: builder.w2l_can_turn_off_watermark,
|
||||
impl PlanBuilder {
|
||||
pub fn new(plan_slug: &str) -> Self {
|
||||
// NB: Not using default() since that seems dangerous when we want to be explicit.
|
||||
PlanBuilder {
|
||||
plan_slug: plan_slug.to_string(),
|
||||
plan_category : PlanCategory::Free,
|
||||
cost_per_month_dollars: None,
|
||||
stripe_product_id: None,
|
||||
stripe_price_id: None,
|
||||
is_development_plan: false,
|
||||
|
||||
// TTS
|
||||
tts_base_priority_level : TTS_DEFAULT_PRIORITY_LEVEL,
|
||||
tts_max_duration: Duration::seconds(TTS_DEFAULT_DURATION_SECONDS),
|
||||
tts_can_generate_mp3 : false,
|
||||
tts_can_upload_private_models: false,
|
||||
tts_can_share_private_models : false,
|
||||
|
||||
// VC
|
||||
vc_max_concurrent_models: VC_DEFAULT_MAX_CONCURRENT_MODELS,
|
||||
vc_pre_recorded_time_limit: Duration::seconds(VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS),
|
||||
vc_pre_recorded_time_is_unlimited: false,
|
||||
vc_real_time_time_limit: Duration::seconds(VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS),
|
||||
vc_real_time_time_is_unlimited: false,
|
||||
|
||||
// W2L
|
||||
w2l_max_duration: Duration::seconds(W2L_DEFAULT_TIME_LIMIT_SECONDS),
|
||||
w2l_can_turn_off_watermark: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Plan {
|
||||
Plan::from_builder(self)
|
||||
}
|
||||
|
||||
pub fn plan_category(mut self, value: PlanCategory) -> Self {
|
||||
self.plan_category = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cost_per_month_dollars(mut self, value: u32) -> Self {
|
||||
self.cost_per_month_dollars = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stripe_product_id(mut self, value: &str) -> Self {
|
||||
self.stripe_product_id = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stripe_price_id(mut self, value: &str) -> Self {
|
||||
self.stripe_price_id = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_development_plan(mut self, value: bool) -> Self {
|
||||
self.is_development_plan = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_base_priority_level(mut self, value: u8) -> Self {
|
||||
self.tts_base_priority_level = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_max_duration_seconds(mut self, value: i64) -> Self {
|
||||
self.tts_max_duration = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_generate_mp3(mut self, value: bool) -> Self {
|
||||
self.tts_can_generate_mp3 = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_upload_private_models(mut self, value: bool) -> Self {
|
||||
self.tts_can_upload_private_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_share_private_models(mut self, value: bool) -> Self {
|
||||
self.tts_can_share_private_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_max_concurrent_models(mut self, value: u32) -> Self {
|
||||
self.vc_max_concurrent_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_pre_recorded_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.vc_pre_recorded_time_limit = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_pre_recorded_time_is_unlimited(mut self, value: bool) -> Self {
|
||||
self.vc_pre_recorded_time_is_unlimited = value;
|
||||
if value {
|
||||
self.vc_pre_recorded_time_limit = Duration::seconds(60 * 60 * 24 * 7);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_real_time_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.vc_real_time_time_limit = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_real_time_time_is_unlimited(mut self, value: bool) -> Self {
|
||||
self.vc_real_time_time_is_unlimited = value;
|
||||
if value {
|
||||
self.vc_real_time_time_limit = Duration::seconds(60 * 60 * 24 * 7);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn w2l_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.w2l_max_duration = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn w2l_can_turn_off_watermark(mut self, value: bool) -> Self {
|
||||
self.w2l_can_turn_off_watermark = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add tests
|
||||
@@ -1,173 +0,0 @@
|
||||
use chrono::Duration;
|
||||
use crate::configs::plans::plan::Plan;
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
|
||||
const TTS_DEFAULT_PRIORITY_LEVEL : u8 = 0;
|
||||
const TTS_DEFAULT_DURATION_SECONDS : i64 = 12;
|
||||
|
||||
const VC_DEFAULT_MAX_CONCURRENT_MODELS : u32 = 5;
|
||||
|
||||
const VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
const VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
|
||||
const W2L_DEFAULT_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlanBuilder {
|
||||
pub plan_name: String,
|
||||
pub plan_category: PlanCategory,
|
||||
pub cost_per_month_dollars: Option<u32>,
|
||||
pub stripe_product_id: Option<String>,
|
||||
pub stripe_price_id: Option<String>,
|
||||
pub is_development_plan: bool,
|
||||
|
||||
// ========== Features for TTS ==========
|
||||
|
||||
pub tts_base_priority_level: u8,
|
||||
pub tts_max_duration: Duration,
|
||||
pub tts_can_generate_mp3: bool,
|
||||
pub tts_can_upload_private_models: bool,
|
||||
pub tts_can_share_private_models: bool,
|
||||
|
||||
// ========== Features for Voice Conversion ==========
|
||||
|
||||
pub vc_max_concurrent_models: u32,
|
||||
|
||||
pub vc_pre_recorded_time_limit: Duration,
|
||||
pub vc_pre_recorded_time_is_unlimited: bool,
|
||||
|
||||
pub vc_real_time_time_limit: Duration,
|
||||
pub vc_real_time_time_is_unlimited: bool,
|
||||
|
||||
// ========== Features for W2L ==========
|
||||
|
||||
pub w2l_max_duration: Duration,
|
||||
pub w2l_can_turn_off_watermark: bool,
|
||||
}
|
||||
|
||||
impl PlanBuilder {
|
||||
pub fn new(plan_name: &str) -> Self {
|
||||
// NB: Not using default() since that seems dangerous when we want to be explicit.
|
||||
PlanBuilder {
|
||||
plan_name : plan_name.to_string(),
|
||||
plan_category : PlanCategory::Free,
|
||||
cost_per_month_dollars: None,
|
||||
stripe_product_id: None,
|
||||
stripe_price_id: None,
|
||||
is_development_plan: false,
|
||||
|
||||
// TTS
|
||||
tts_base_priority_level : TTS_DEFAULT_PRIORITY_LEVEL,
|
||||
tts_max_duration: Duration::seconds(TTS_DEFAULT_DURATION_SECONDS),
|
||||
tts_can_generate_mp3 : false,
|
||||
tts_can_upload_private_models: false,
|
||||
tts_can_share_private_models : false,
|
||||
|
||||
// VC
|
||||
vc_max_concurrent_models: VC_DEFAULT_MAX_CONCURRENT_MODELS,
|
||||
vc_pre_recorded_time_limit: Duration::seconds(VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS),
|
||||
vc_pre_recorded_time_is_unlimited: false,
|
||||
vc_real_time_time_limit: Duration::seconds(VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS),
|
||||
vc_real_time_time_is_unlimited: false,
|
||||
|
||||
// W2L
|
||||
w2l_max_duration: Duration::seconds(W2L_DEFAULT_TIME_LIMIT_SECONDS),
|
||||
w2l_can_turn_off_watermark: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Plan {
|
||||
Plan::from_builder(self)
|
||||
}
|
||||
|
||||
pub fn plan_category(mut self, value: PlanCategory) -> Self {
|
||||
self.plan_category = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cost_per_month_dollars(mut self, value: u32) -> Self {
|
||||
self.cost_per_month_dollars = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stripe_product_id(mut self, value: &str) -> Self {
|
||||
self.stripe_product_id = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn stripe_price_id(mut self, value: &str) -> Self {
|
||||
self.stripe_price_id = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_development_plan(mut self, value: bool) -> Self {
|
||||
self.is_development_plan = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_base_priority_level(mut self, value: u8) -> Self {
|
||||
self.tts_base_priority_level = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_max_duration_seconds(mut self, value: i64) -> Self {
|
||||
self.tts_max_duration = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_generate_mp3(mut self, value: bool) -> Self {
|
||||
self.tts_can_generate_mp3 = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_upload_private_models(mut self, value: bool) -> Self {
|
||||
self.tts_can_upload_private_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tts_can_share_private_models(mut self, value: bool) -> Self {
|
||||
self.tts_can_share_private_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_max_concurrent_models(mut self, value: u32) -> Self {
|
||||
self.vc_max_concurrent_models = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_pre_recorded_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.vc_pre_recorded_time_limit = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_pre_recorded_time_is_unlimited(mut self, value: bool) -> Self {
|
||||
self.vc_pre_recorded_time_is_unlimited = value;
|
||||
if value {
|
||||
self.vc_pre_recorded_time_limit = Duration::seconds(60 * 60 * 24 * 7);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_real_time_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.vc_real_time_time_limit = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_real_time_time_is_unlimited(mut self, value: bool) -> Self {
|
||||
self.vc_real_time_time_is_unlimited = value;
|
||||
if value {
|
||||
self.vc_real_time_time_limit = Duration::seconds(60 * 60 * 24 * 7);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn w2l_time_limit_seconds(mut self, value: i64) -> Self {
|
||||
self.w2l_max_duration = Duration::seconds(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn w2l_can_turn_off_watermark(mut self, value: bool) -> Self {
|
||||
self.w2l_can_turn_off_watermark = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use tts_common::priority::{FAKEYOU_ANONYMOUS_PRIORITY_LEVEL, FAKEYOU_LOGGED_IN_PRIORITY_LEVEL};
|
||||
use crate::configs::plans::plan::Plan;
|
||||
use crate::configs::plans::plan_builder::PlanBuilder;
|
||||
use crate::configs::plans::plan::PlanBuilder;
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tts_common::priority::{FAKEYOU_ANONYMOUS_PRIORITY_LEVEL, FAKEYOU_LOGGED_IN_PRIORITY_LEVEL};
|
||||
|
||||
/// The plan for free logged-out users.
|
||||
/// This is declared at the top level because it may need to be referenced directly.
|
||||
pub static FREE_LOGGED_OUT_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("free_logged_out")
|
||||
.plan_category(PlanCategory::Free)
|
||||
@@ -12,6 +14,17 @@ pub static FREE_LOGGED_OUT_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
.build()
|
||||
});
|
||||
|
||||
/// A plan for free logged-out users on their first few attempts at using the website.
|
||||
/// This is declared at the top level because it may need to be referenced directly.
|
||||
pub static FREE_LOGGED_OUT_FIRST_TRY_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("free_logged_out_first_try")
|
||||
.plan_category(PlanCategory::Free)
|
||||
.tts_base_priority_level(FAKEYOU_LOGGED_IN_PRIORITY_LEVEL) // NB: Same as logged-in free users.
|
||||
.build()
|
||||
});
|
||||
|
||||
/// The plan for free logged-in users.
|
||||
/// This is declared at the top level because it may need to be referenced directly.
|
||||
pub static FREE_LOGGED_IN_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("free_logged_in")
|
||||
.plan_category(PlanCategory::Free)
|
||||
@@ -20,45 +33,288 @@ pub static FREE_LOGGED_IN_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
});
|
||||
|
||||
/// This plan is for users that create models for us but don't pay.
|
||||
/// This is declared at the top level because it may need to be referenced directly.
|
||||
pub static LOYALTY_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("loyalty_plan")
|
||||
.plan_category(PlanCategory::LoyaltyReward)
|
||||
.tts_base_priority_level(2)
|
||||
.tts_max_duration_seconds(30)
|
||||
.build()
|
||||
});
|
||||
|
||||
// TODO: Add by-[stripe-product] lookup HashSets.
|
||||
|
||||
pub static DEVELOPMENT_PLANS : Lazy<HashSet<Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashSet::new();
|
||||
|
||||
// ========== English plans ==========
|
||||
|
||||
plans.insert(PlanBuilder::new("development_fakeyou_en_plus")
|
||||
.is_development_plan(true)
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.stripe_product_id("prod_MMxi2J5y69VPbO")
|
||||
.stripe_price_id("price_1LeDnKEU5se17MekVr1iYYNf")
|
||||
.cost_per_month_dollars(5)
|
||||
.tts_base_priority_level(10)
|
||||
.tts_max_duration_seconds(30)
|
||||
.build());
|
||||
|
||||
plans.insert(PlanBuilder::new("development_fakeyou_en_pro")
|
||||
.is_development_plan(true)
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.stripe_product_id("prod_MScAZa5uk5TfDY")
|
||||
.stripe_price_id("price_1LjgwIEU5se17MekzQZUHl9W")
|
||||
.cost_per_month_dollars(15)
|
||||
.tts_base_priority_level(20)
|
||||
.tts_max_duration_seconds(120)
|
||||
.build());
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
/// All premium / paid-for plans in the system.
|
||||
pub static PREMIUM_PLANS : Lazy<HashSet<Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashSet::new();
|
||||
|
||||
// ========== English plans ==========
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_en_basic")
|
||||
plans.insert(PlanBuilder::new("fakeyou_en_plus")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(5)
|
||||
.cost_per_month_dollars(7)
|
||||
.tts_base_priority_level(10)
|
||||
.tts_max_duration_seconds(30)
|
||||
.build());
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_en_pro")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(15)
|
||||
.tts_base_priority_level(20)
|
||||
.tts_max_duration_seconds(120)
|
||||
.build());
|
||||
|
||||
|
||||
// ========== Spanish plans ==========
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_es_basic")
|
||||
plans.insert(PlanBuilder::new("fakeyou_es_plus")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(3)
|
||||
.tts_base_priority_level(5)
|
||||
.tts_max_duration_seconds(30)
|
||||
.build());
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_es_pro")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(7)
|
||||
.tts_base_priority_level(11)
|
||||
.tts_base_priority_level(15)
|
||||
.tts_max_duration_seconds(45)
|
||||
.build());
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
/// Every single plan in the system: free, paid, loyalty-based, and Stripe development plans.
|
||||
pub static ALL_PLANS_BY_SLUG : Lazy<HashMap<String, Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashMap::new();
|
||||
|
||||
fn add_plan(plans: &mut HashMap<String, Plan>, plan: &Plan) {
|
||||
plans.insert(plan.plan_slug().to_string(), plan.clone());
|
||||
}
|
||||
|
||||
add_plan(&mut plans, &FREE_LOGGED_OUT_PLAN);
|
||||
add_plan(&mut plans, &FREE_LOGGED_OUT_FIRST_TRY_PLAN);
|
||||
add_plan(&mut plans, &FREE_LOGGED_IN_PLAN);
|
||||
add_plan(&mut plans, &LOYALTY_PLAN);
|
||||
|
||||
PREMIUM_PLANS.iter().for_each(|plan| add_plan(&mut plans, plan));
|
||||
DEVELOPMENT_PLANS .iter().for_each(|plan| add_plan(&mut plans, plan));
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
/// Every Stripe premium product by its Stripe product id.
|
||||
pub static PLANS_BY_STRIPE_PRODUCT_ID : Lazy<HashMap<String, Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashMap::new();
|
||||
|
||||
ALL_PLANS_BY_SLUG.values()
|
||||
.for_each(|plan| {
|
||||
if let Some(stripe_product_id) = plan.stripe_product_id() {
|
||||
plans.insert(stripe_product_id.to_string(), plan.clone());
|
||||
}
|
||||
});
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
/// Every Stripe premium product by its Stripe price id.
|
||||
pub static PLANS_BY_STRIPE_PRICE_ID : Lazy<HashMap<String, Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashMap::new();
|
||||
|
||||
ALL_PLANS_BY_SLUG.values()
|
||||
.for_each(|plan| {
|
||||
if let Some(stripe_price_id) = plan.stripe_price_id() {
|
||||
plans.insert(stripe_price_id.to_string(), plan.clone());
|
||||
}
|
||||
});
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
// TODO: Add tests to assert our expectations of each plan.
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::configs::plans::plan::Plan;
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
use crate::configs::plans::plan_list::{ALL_PLANS_BY_SLUG, DEVELOPMENT_PLANS, PLANS_BY_STRIPE_PRICE_ID, PLANS_BY_STRIPE_PRODUCT_ID, PREMIUM_PLANS};
|
||||
use speculoos::prelude::*;
|
||||
|
||||
// NB: We're being extremely careful in this test and all those that follow, essentially
|
||||
// making ourselves check twice when we make additions, removals, or other changes.
|
||||
#[test]
|
||||
fn test_number_of_plans_is_expected() {
|
||||
assert_eq!(10, ALL_PLANS_BY_SLUG.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_of_production_plans_is_expected() {
|
||||
let production_plans = ALL_PLANS_BY_SLUG.values()
|
||||
.filter(|plan| !plan.is_development_plan())
|
||||
.collect::<Vec<&Plan>>();
|
||||
|
||||
assert_eq!(8, production_plans.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_of_development_plans_is_expected() {
|
||||
let development_plans = ALL_PLANS_BY_SLUG.values()
|
||||
.filter(|plan| plan.is_development_plan())
|
||||
.collect::<Vec<&Plan>>();
|
||||
|
||||
assert_eq!(2, development_plans.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plans_by_stripe_product_id_are_expected() {
|
||||
let product_ids = PLANS_BY_STRIPE_PRODUCT_ID.keys()
|
||||
.map(|product_id| product_id.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected_product_ids = vec![
|
||||
"prod_MMxi2J5y69VPbO".to_string(), // NB: Development
|
||||
"prod_MScAZa5uk5TfDY".to_string(), // NB: Development
|
||||
];
|
||||
|
||||
assert_that(&product_ids).contains_all_of(&expected_product_ids.iter());
|
||||
assert_eq!(product_ids.len(), expected_product_ids.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plans_by_stripe_price_id_are_expected() {
|
||||
let price_ids = PLANS_BY_STRIPE_PRICE_ID.keys()
|
||||
.map(|price_id| price_id.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected_price_ids = vec![
|
||||
"price_1LjgwIEU5se17MekzQZUHl9W".to_string(), // NB: Development
|
||||
"price_1LeDnKEU5se17MekVr1iYYNf".to_string(), // NB: Development
|
||||
];
|
||||
|
||||
assert_that(&price_ids).contains_all_of(&expected_price_ids.iter());
|
||||
assert_eq!(price_ids.len(), expected_price_ids.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tts_base_priority_levels_are_expected() {
|
||||
// Free
|
||||
assert_eq!(0, ALL_PLANS_BY_SLUG.get("free_logged_out").unwrap().tts_base_priority_level());
|
||||
assert_eq!(1, ALL_PLANS_BY_SLUG.get("free_logged_out_first_try").unwrap().tts_base_priority_level());
|
||||
assert_eq!(1, ALL_PLANS_BY_SLUG.get("free_logged_in").unwrap().tts_base_priority_level());
|
||||
|
||||
// Loyalty-based
|
||||
assert_eq!(2, ALL_PLANS_BY_SLUG.get("loyalty_plan").unwrap().tts_base_priority_level());
|
||||
|
||||
// Premium
|
||||
assert_eq!(5, ALL_PLANS_BY_SLUG.get("fakeyou_es_plus").unwrap().tts_base_priority_level());
|
||||
assert_eq!(10, ALL_PLANS_BY_SLUG.get("fakeyou_en_plus").unwrap().tts_base_priority_level());
|
||||
assert_eq!(15, ALL_PLANS_BY_SLUG.get("fakeyou_es_pro").unwrap().tts_base_priority_level());
|
||||
assert_eq!(20, ALL_PLANS_BY_SLUG.get("fakeyou_en_pro").unwrap().tts_base_priority_level());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tts_max_durations_are_expected() {
|
||||
// Free
|
||||
assert_eq!(12, ALL_PLANS_BY_SLUG.get("free_logged_out").unwrap().tts_max_duration().num_seconds());
|
||||
assert_eq!(12, ALL_PLANS_BY_SLUG.get("free_logged_out_first_try").unwrap().tts_max_duration().num_seconds());
|
||||
assert_eq!(12, ALL_PLANS_BY_SLUG.get("free_logged_in").unwrap().tts_max_duration().num_seconds());
|
||||
|
||||
// Loyalty-based
|
||||
assert_eq!(30, ALL_PLANS_BY_SLUG.get("loyalty_plan").unwrap().tts_max_duration().num_seconds());
|
||||
|
||||
// Premium
|
||||
assert_eq!(30, ALL_PLANS_BY_SLUG.get("fakeyou_es_plus").unwrap().tts_max_duration().num_seconds());
|
||||
assert_eq!(30, ALL_PLANS_BY_SLUG.get("fakeyou_en_plus").unwrap().tts_max_duration().num_seconds());
|
||||
assert_eq!(45, ALL_PLANS_BY_SLUG.get("fakeyou_es_pro").unwrap().tts_max_duration().num_seconds());
|
||||
assert_eq!(120, ALL_PLANS_BY_SLUG.get("fakeyou_en_pro").unwrap().tts_max_duration().num_seconds());
|
||||
}
|
||||
|
||||
// The following tests should not need to change much
|
||||
|
||||
#[test]
|
||||
fn test_assert_all_premium_plans_are_paid() {
|
||||
let mut test_count = 0;
|
||||
|
||||
PREMIUM_PLANS.iter().for_each(|plan| {
|
||||
assert_eq!(plan.plan_category(), PlanCategory::Paid);
|
||||
test_count += 1;
|
||||
});
|
||||
|
||||
assert!(test_count > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assert_all_development_stripe_plans_are_marked_development() {
|
||||
let mut test_count = 0;
|
||||
|
||||
DEVELOPMENT_PLANS.iter().for_each(|plan| {
|
||||
assert!(plan.is_development_plan());
|
||||
test_count += 1;
|
||||
});
|
||||
|
||||
assert!(test_count > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assert_correct_key_for_plans_indexed_by_slug() {
|
||||
let mut test_count = 0;
|
||||
|
||||
ALL_PLANS_BY_SLUG.iter().for_each(|(slug, plan)| {
|
||||
assert_eq!(slug, plan.plan_slug());
|
||||
test_count += 1;
|
||||
});
|
||||
|
||||
assert!(test_count > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assert_correct_key_for_plans_indexed_by_product_id() {
|
||||
let mut test_count = 0;
|
||||
|
||||
PLANS_BY_STRIPE_PRODUCT_ID.iter().for_each(|(product_id, plan)| {
|
||||
assert_eq!(product_id, plan.stripe_product_id().unwrap());
|
||||
test_count += 1;
|
||||
});
|
||||
|
||||
assert!(test_count > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assert_correct_key_for_plans_indexed_by_price_id() {
|
||||
let mut test_count = 0;
|
||||
|
||||
PLANS_BY_STRIPE_PRICE_ID.iter().for_each(|(price_id, plan)| {
|
||||
assert_eq!(price_id, plan.stripe_price_id().unwrap());
|
||||
test_count += 1;
|
||||
});
|
||||
|
||||
assert!(test_count > 1);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user