mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
new subscriptions endpoint
This commit is contained in:
1 parent
4bb3f6af2d
commit
2e024efc32
11 files changed
+178
-45
No files matched your search
@@ -4,4 +4,5 @@ pub mod generate;
|
||||
pub mod jobs;
|
||||
pub mod media_file;
|
||||
pub mod prompts;
|
||||
pub mod stripe_artcraft;
|
||||
pub mod stripe_artcraft;
|
||||
pub mod subscriptions;
|
||||
@@ -0,0 +1,24 @@
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::user_subscriptions::UserSubscriptionToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GetSessionSubscriptionResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// Information about the user's subscription, if any.
|
||||
pub active_subscription: Option<SubscriptionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct SubscriptionInfo {
|
||||
/// Unique token for the subscription.
|
||||
pub subscription_token: UserSubscriptionToken,
|
||||
|
||||
/// Namespace for the subscription.
|
||||
pub namespace: PaymentsNamespace,
|
||||
|
||||
/// Local key for the subscription.
|
||||
pub product_slug: String,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod get_session_subscription;
|
||||
+26
-20
@@ -2,19 +2,21 @@ use crate::errors::select_optional_record_error::SelectOptionalRecordError;
|
||||
use crate::helpers::boolean_converters::nullable_i8_to_bool_default_false;
|
||||
use crate::types::query_map::QueryMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug;
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use sqlx;
|
||||
use sqlx::mysql::MySqlRow;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::MySql;
|
||||
use tokens::tokens::user_subscriptions::UserSubscriptionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct ArtcraftSubscription {
|
||||
pub struct UserSubscription {
|
||||
pub token: UserSubscriptionToken,
|
||||
|
||||
pub user_token: UserToken,
|
||||
|
||||
pub subscription_namespace: PaymentsNamespace,
|
||||
pub subscription_product_slug: ArtcraftSubscriptionSlug,
|
||||
pub subscription_product_slug: String,
|
||||
|
||||
pub stripe_customer_id: String,
|
||||
pub stripe_subscription_id: String,
|
||||
@@ -33,12 +35,13 @@ pub struct ArtcraftSubscription {
|
||||
/// Technically, there may be more than one subscription record.
|
||||
/// We sort the results and only return the first active artcraft subscription
|
||||
/// by numeric ID so we should have a consistent view.
|
||||
pub async fn find_artcraft_subscription_for_owner_user_using_connection(
|
||||
pub async fn find_subscription_for_owner_user_using_connection(
|
||||
user_token: &UserToken,
|
||||
namespace: PaymentsNamespace,
|
||||
connection: &mut PoolConnection<MySql>,
|
||||
) -> Result<Option<ArtcraftSubscription>, SelectOptionalRecordError> {
|
||||
) -> Result<Option<UserSubscription>, SelectOptionalRecordError> {
|
||||
|
||||
let query = query(user_token);
|
||||
let query = query(user_token, namespace);
|
||||
|
||||
let result = query
|
||||
.fetch_optional(&mut **connection)
|
||||
@@ -48,12 +51,13 @@ pub async fn find_artcraft_subscription_for_owner_user_using_connection(
|
||||
}
|
||||
|
||||
|
||||
pub async fn find_artcraft_subscription_for_owner_user_using_transaction(
|
||||
pub async fn find_subscription_for_owner_user_using_transaction(
|
||||
user_token: &UserToken,
|
||||
namespace: PaymentsNamespace,
|
||||
transaction: &mut sqlx::Transaction<'_, MySql>,
|
||||
) -> Result<Option<ArtcraftSubscription>, SelectOptionalRecordError> {
|
||||
) -> Result<Option<UserSubscription>, SelectOptionalRecordError> {
|
||||
|
||||
let query = query(user_token);
|
||||
let query = query(user_token, namespace);
|
||||
|
||||
let result = query
|
||||
.fetch_optional(&mut **transaction)
|
||||
@@ -62,9 +66,10 @@ pub async fn find_artcraft_subscription_for_owner_user_using_transaction(
|
||||
map_result(result)
|
||||
}
|
||||
|
||||
fn map_result(result: Result<Option<RawArtcraftSubscription>, sqlx::Error>) -> Result<Option<ArtcraftSubscription>, SelectOptionalRecordError> {
|
||||
fn map_result(result: Result<Option<RawUserSubscription>, sqlx::Error>) -> Result<Option<UserSubscription>, SelectOptionalRecordError> {
|
||||
match result {
|
||||
Ok(Some(record)) => Ok(Some(ArtcraftSubscription {
|
||||
Ok(Some(record)) => Ok(Some(UserSubscription {
|
||||
token: record.token,
|
||||
user_token: record.user_token,
|
||||
subscription_namespace: record.subscription_namespace,
|
||||
subscription_product_slug: record.subscription_product_slug,
|
||||
@@ -84,22 +89,21 @@ fn map_result(result: Result<Option<RawArtcraftSubscription>, sqlx::Error>) -> R
|
||||
}
|
||||
|
||||
// QueryAs<'q, Postgres, O, PgArguments>
|
||||
fn query(user_token: &UserToken)
|
||||
-> QueryMap<impl Send + FnMut(MySqlRow) -> Result<RawArtcraftSubscription, sqlx::Error>>
|
||||
fn query(user_token: &UserToken, namespace: PaymentsNamespace)
|
||||
-> QueryMap<impl Send + FnMut(MySqlRow) -> Result<RawUserSubscription, sqlx::Error>>
|
||||
{
|
||||
const ARTCRAFT_NAMESPACE: &str = PaymentsNamespace::Artcraft.to_str();
|
||||
|
||||
// NB: We want to eventually support multiple subscriptions per user (eg. company use case),
|
||||
// so we do not have a unique key on user token (but we do on stripe subscription id).
|
||||
// In the meantime, to ensure we use the same subscription each time, we order by id and
|
||||
// take the first one.
|
||||
sqlx::query_as!(
|
||||
RawArtcraftSubscription,
|
||||
RawUserSubscription,
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,
|
||||
user_token as `user_token: tokens::tokens::users::UserToken`,
|
||||
subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,
|
||||
subscription_product_slug as `subscription_product_slug: enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug`,
|
||||
subscription_product_slug,
|
||||
maybe_stripe_customer_id,
|
||||
maybe_stripe_subscription_id,
|
||||
maybe_stripe_subscription_status,
|
||||
@@ -121,17 +125,19 @@ WHERE
|
||||
LIMIT 1
|
||||
"#,
|
||||
user_token.as_str(),
|
||||
ARTCRAFT_NAMESPACE,
|
||||
namespace.to_str(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct RawArtcraftSubscription {
|
||||
struct RawUserSubscription {
|
||||
token: UserSubscriptionToken,
|
||||
|
||||
user_token: UserToken,
|
||||
|
||||
subscription_namespace: PaymentsNamespace,
|
||||
subscription_product_slug: ArtcraftSubscriptionSlug,
|
||||
subscription_product_slug: String,
|
||||
|
||||
maybe_stripe_customer_id: Option<String>,
|
||||
maybe_stripe_subscription_id: Option<String>,
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod find_artcraft_subscription_for_owner_user;
|
||||
pub mod find_subscription_for_owner_user;
|
||||
pub mod get_user_subscription_by_stripe_subscription_id;
|
||||
pub mod get_user_subscription_by_stripe_subscription_id_transactional;
|
||||
pub mod list_active_user_subscriptions;
|
||||
|
||||
+3
-2
@@ -11,7 +11,7 @@ use component_traits::traits::internal_user_lookup::InternalUserLookup;
|
||||
use enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug;
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::users::user_subscriptions::find_artcraft_subscription_for_owner_user::find_artcraft_subscription_for_owner_user_using_connection;
|
||||
use mysql_queries::queries::users::user_subscriptions::find_subscription_for_owner_user::find_subscription_for_owner_user_using_connection;
|
||||
use reusable_types::server_environment::ServerEnvironment;
|
||||
use sqlx::MySqlPool;
|
||||
use std::collections::HashMap;
|
||||
@@ -72,8 +72,9 @@ pub async fn stripe_artcraft_create_customer_portal_session_handler(
|
||||
|
||||
let user_token = UserToken::new_from_str(&user_metadata.user_token);
|
||||
|
||||
let result = find_artcraft_subscription_for_owner_user_using_connection(
|
||||
let result = find_subscription_for_owner_user_using_connection(
|
||||
&user_token,
|
||||
PaymentsNamespace::Artcraft,
|
||||
&mut mysql_connection
|
||||
).await;
|
||||
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse};
|
||||
use artcraft_api_defs::credits::get_session_credits::GetSessionCreditsResponse;
|
||||
use artcraft_api_defs::subscriptions::get_session_subscription::{GetSessionSubscriptionResponse, SubscriptionInfo};
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use log::{error, warn};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use utoipa::ToSchema;
|
||||
use mysql_queries::queries::users::user_subscriptions::find_subscription_for_owner_user::find_subscription_for_owner_user_using_connection;
|
||||
|
||||
/// For the URL PathInfo
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct GetSessionSubscriptionPathInfo {
|
||||
namespace: PaymentsNamespace,
|
||||
}
|
||||
|
||||
/// Get subscription for the payment namespace.
|
||||
///
|
||||
/// Use this instead of the older endpoints,
|
||||
///
|
||||
/// - GET /v1/billing/active_subscription (legacy, for FakeYou)
|
||||
/// - GET /v1/app_state (quite a lot of FakeYou state)
|
||||
///
|
||||
#[utoipa::path(
|
||||
get,
|
||||
tag = "Subscriptions",
|
||||
path = "/v1/subscriptions/namespace/{namespace}",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = GetSessionCreditsResponse),
|
||||
),
|
||||
params(
|
||||
("path" = GetSessionSubscriptionPathInfo, description = "Path for Request")
|
||||
)
|
||||
)]
|
||||
pub async fn get_session_subscription_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<GetSessionSubscriptionPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<GetSessionSubscriptionResponse>, CommonWebError>
|
||||
{
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error acquiring MySQL connection: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let user_token = match maybe_user_session {
|
||||
Some(session) => session.user_token,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
|
||||
let maybe_subscription = find_subscription_for_owner_user_using_connection(
|
||||
&user_token,
|
||||
path.namespace,
|
||||
&mut mysql_connection
|
||||
).await.map_err(|err| {
|
||||
error!("Error looking up user's ({}) existing subscription: {:?}", &user_token, err);
|
||||
CommonWebError::ServerError // NB: This was probably *our* fault.
|
||||
})?;
|
||||
|
||||
Ok(Json(GetSessionSubscriptionResponse {
|
||||
success: true,
|
||||
active_subscription: maybe_subscription.map(|sub| SubscriptionInfo {
|
||||
namespace: sub.subscription_namespace,
|
||||
product_slug: sub.subscription_product_slug,
|
||||
subscription_token: sub.token,
|
||||
}),
|
||||
}))
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod get_session_subscription_handler;
|
||||
pub mod unsubscribe_reason;
|
||||
@@ -130,6 +130,7 @@ use crate::http_server::routes::add_generate_routes::add_generate_routes;
|
||||
use crate::http_server::routes::add_image_studio_routes::add_image_studio_routes;
|
||||
use crate::http_server::routes::add_stripe_artcraft_routes::add_stripe_artcraft_routes;
|
||||
use crate::http_server::routes::add_studio_gen2_routes::add_studio_gen2_routes;
|
||||
use crate::http_server::routes::add_subscription_routes::add_subscription_routes;
|
||||
use crate::http_server::routes::add_webhook_routes::add_webhook_routes;
|
||||
use crate::http_server::routes::beta_key_routes::add_beta_key_routes;
|
||||
use crate::http_server::routes::job_routes::add_job_routes;
|
||||
@@ -893,26 +894,6 @@ fn add_featured_item_routes<T, B> (app: App<T>) -> App<T>
|
||||
)
|
||||
}
|
||||
|
||||
// ============= SUBSCRIPTION ROUTES ===================
|
||||
fn add_subscription_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/subscriptions")
|
||||
.service(web::resource("/unsubscribe_reason")
|
||||
.route(web::post().to(set_unsubscribe_reason_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn add_voice_designer_routes<T,B> (app:App<T>)-> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::http_server::endpoints::subscriptions::get_session_subscription_handler::get_session_subscription_handler;
|
||||
use crate::http_server::endpoints::subscriptions::unsubscribe_reason::set_unsubscribe_reason_handler;
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::{web, App, Error, HttpResponse};
|
||||
|
||||
pub fn add_subscription_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/subscriptions")
|
||||
.service(web::resource("/unsubscribe_reason")
|
||||
.route(web::post().to(set_unsubscribe_reason_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/namespace/{namespace}")
|
||||
.route(web::post().to(get_session_subscription_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -14,4 +14,5 @@ mod tag_routes;
|
||||
mod user_routes;
|
||||
mod weights_routes;
|
||||
mod workflow_routes;
|
||||
pub mod add_routes;
|
||||
pub mod add_routes;
|
||||
mod add_subscription_routes;
|
||||
Reference in new issue
Block a user