mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
Premium plans config (just getting started on this)
This commit is contained in:
1 parent
1ca54927d7
commit
4e4dbf0688
7 files changed
+330
-1
No files matched your search
@@ -70,7 +70,6 @@ pub async fn stripe_webhook_handler(
|
||||
StripeWebhookError::BadRequest
|
||||
})?;
|
||||
|
||||
|
||||
// Events can be re-sent, so we need to make handling them idempotent.
|
||||
let stripe_event_id = webhook_payload.id.to_string();
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod plans;
|
||||
pub mod static_api_tokens;
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod plan;
|
||||
pub mod plan_builder;
|
||||
pub mod plan_category;
|
||||
pub mod plan_list;
|
||||
@@ -0,0 +1,77 @@
|
||||
use chrono::Duration;
|
||||
use crate::configs::plans::plan_builder::PlanBuilder;
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Whether the plan is free, paid, or loyalty-based
|
||||
plan_category: PlanCategory,
|
||||
|
||||
/// Non-authoritative cost of the plan per month in USD equivalent.
|
||||
/// This is mostly for documentation. Stripe will remain the source
|
||||
/// of truth.
|
||||
cost_per_month_dollars: Option<u32>,
|
||||
|
||||
/// For lookup against Stripe product ID
|
||||
stripe_product_id: Option<String>,
|
||||
|
||||
/// For lookup against Stripe price ID
|
||||
stripe_price_id: Option<String>,
|
||||
|
||||
/// Whether this plan is only active in development
|
||||
is_development_plan: bool,
|
||||
|
||||
// ========== Features for TTS ==========
|
||||
|
||||
tts_base_priority_level: u8,
|
||||
tts_max_duration: Duration,
|
||||
tts_can_generate_mp3: bool,
|
||||
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_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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum PlanCategory {
|
||||
/// Free plans cost nothing and are the lowest tiers of service.
|
||||
Free,
|
||||
/// Certain users will get premium features for free by contributing to FakeYou.
|
||||
LoyaltyReward,
|
||||
/// Paid plans come with various variable premium perks according to plan level.
|
||||
Paid,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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_category::PlanCategory;
|
||||
|
||||
pub static FREE_LOGGED_OUT_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("free_logged_out")
|
||||
.plan_category(PlanCategory::Free)
|
||||
.tts_base_priority_level(FAKEYOU_ANONYMOUS_PRIORITY_LEVEL)
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static FREE_LOGGED_IN_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("free_logged_in")
|
||||
.plan_category(PlanCategory::Free)
|
||||
.tts_base_priority_level(FAKEYOU_LOGGED_IN_PRIORITY_LEVEL)
|
||||
.build()
|
||||
});
|
||||
|
||||
/// This plan is for users that create models for us but don't pay.
|
||||
pub static LOYALTY_PLAN : Lazy<Plan> = Lazy::new(|| {
|
||||
PlanBuilder::new("loyalty_plan")
|
||||
.plan_category(PlanCategory::LoyaltyReward)
|
||||
.tts_base_priority_level(2)
|
||||
.build()
|
||||
});
|
||||
|
||||
pub static PREMIUM_PLANS : Lazy<HashSet<Plan>> = Lazy::new(|| {
|
||||
let mut plans = HashSet::new();
|
||||
|
||||
// ========== English plans ==========
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_en_basic")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(5)
|
||||
.tts_base_priority_level(10)
|
||||
.build());
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_en_pro")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(15)
|
||||
.tts_base_priority_level(20)
|
||||
.build());
|
||||
|
||||
|
||||
// ========== Spanish plans ==========
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_es_basic")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(3)
|
||||
.tts_base_priority_level(5)
|
||||
.build());
|
||||
|
||||
plans.insert(PlanBuilder::new("fakeyou_es_pro")
|
||||
.plan_category(PlanCategory::Paid)
|
||||
.cost_per_month_dollars(7)
|
||||
.tts_base_priority_level(11)
|
||||
.build());
|
||||
|
||||
plans
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user