stripe info admin endpoint (#1623)

This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-06-10 08:42:32 -04:00
1 parent a7c59f7c9b
commit 1c654bfafa
22 files changed
+439 -2

No files matched your search

@@ -0,0 +1,36 @@
{
"db_name": "MySQL",
"query": "\nSELECT DISTINCT\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n maybe_stripe_customer_id as `stripe_customer_id!`\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND deleted_at IS NULL\nORDER BY subscription_namespace ASC, maybe_stripe_customer_id ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "subscription_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "stripe_customer_id!",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true
]
},
"hash": "516db34b6a642a2f7d7107f186905a79ad694c3c86f3078efc77bcaae7f39f3d"
}
@@ -0,0 +1,47 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n user_token as `user_token: tokens::tokens::users::UserToken`,\n payments_namespace as `payments_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n stripe_customer_id\n\nFROM user_stripe_customer_links\n\nWHERE user_token = ?\nORDER BY id ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "payments_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "ffb5717340f21f21693bfcade201bc055fb3fb8a26da8d0574e3de44b033586c"
}
@@ -2,5 +2,6 @@ pub mod alerts;
pub mod jobs;
pub mod user;
pub mod user_referrals;
pub mod user_stripe_data;
pub mod wallet_ledger_entries;
pub mod wallets;
@@ -0,0 +1 @@
pub mod moderator_get_user_stripe_customer_ids;
@@ -0,0 +1,40 @@
use enums::common::payments_namespace::PaymentsNamespace;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
pub const MODERATOR_GET_USER_STRIPE_CUSTOMER_IDS_PATH: &str =
"/v1/moderation/user_stripe_data/{user_token}/customer_ids";
#[derive(Deserialize, ToSchema)]
pub struct ModeratorGetUserStripeCustomerIdsPathInfo {
pub user_token: UserToken,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorGetUserStripeCustomerIdsResponse {
pub success: bool,
/// All stripe customer ids we have on file for the user, across payments
/// namespaces. A user may have zero or several: a customer link and/or
/// subscriptions per namespace (eg. artcraft), plus legacy fakeyou
/// subscriptions. The same customer id can appear once per source.
pub customer_ids: Vec<ModeratorUserStripeCustomerIdEntry>,
}
#[derive(Serialize, ToSchema)]
pub struct ModeratorUserStripeCustomerIdEntry {
pub stripe_customer_id: String,
pub payments_namespace: PaymentsNamespace,
pub source: ModeratorStripeCustomerIdSource,
}
/// Which table the customer id was found in.
#[derive(Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ModeratorStripeCustomerIdSource {
/// From the `user_stripe_customer_links` table (1:1 per namespace).
CustomerLink,
/// From the user's rows in the `user_subscriptions` table.
Subscription,
}
@@ -0,0 +1,18 @@
{
"type": "image",
"blob": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCATgA0ADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDpBThTRThTJHinimCn0hjhS00U6kMKWkoqQFpRSUtAx1LTaWgB1LTaXNAxaSiigANNpTQaZIlJS0UxCUUuKSmAUtAooGFFFFIYYopaKAEpKWimJjTSU40lBI2ilxRikIQU8UgFKKBjhTxTBTxSGLS0lFMYtNNLSGgBppppxpKAGGkxTqKBCYoFLiigBRThSCnCmAtLSUtMYtFFFABRRRSASig0lJjCkNFJUgJSUppppAFJS0UCEpaKUUigpRRSigYopaQUopgLTqbS00SxRSikopiFzRmkozQAZpKM0maQAabS5pKBiGkNBpKYCUUUlMBaM0lGaBDqKSloAWlpKKAFptLSUxCUhpaQ0ANNJS0lACUlLijFIY2inYpMUgDFGKXFLipYxKdRilFJgUxTxTRTxWpI4U4U0U4UhjhS0gpaQwpaSjNIBaWkooAWlpKKYDqWm0tAC0tJRQAUUUYpkhikpcUUwEooooAKKKKQwpaSloGFLikFLQAlFLSUCEpMU6kpiG4oxTsUYpCsJSgUYpRQMUU4UgpaAFooooGFJS0lIBDTadSUANopaKAExS4oxS0AGKWilFMApaSloAKWiimAUlKaSkMSiikpAIaTNBpKACkoopAFFFFIApaKWgApaQUtIYtKKSlpgLS02lqhC0UlFMQtJmikoAWkopKQBSUUlAAaaaU0hoGJSZpaSmAUUUUALRSUtAC0uaSimAZooooEJSUtJQMSkpaKQCUUtLQA3FLilxS4pANxS4pcUYqRiYpaMUtICpilFLilxWhICnCkFLikMWloFLSGFFFFABRRRTELS02loAdS0gpaBBmlpKWmAtFFFMQUUYooASilooAbRS0UhiUtFFIYtFFFAgopaKYCYpKdikoASilooCwlLS0UAFLRS0gCilooASg0tJQA2ilopANxRilooGJS0UUAFLSUtMAFLQKBQAtFFFAAaaaWkNACGmmnGmmgBKSlNJSGFFFFAgpRSUtAgpaSlpDFFFJRQMWlpKKYDqKSlpiFzRmkopiCkoooAKSikoAKSlpKBiUhpaSkAlFLSUAFFFFMYUtFFABS0YooAKSlpKAENFKaSgAxRiiigApaKWgAxRS0UhhiilxRSASiiikBXxS4p2KMVZIgFGKdiikMSloooAKKKKQBRRRTAKWkpaBCinU0U4UAFKKBRTELS0lKKYgooooAMUlLRQMSkpaKQxKWiikAUtJS0AFFFFMAooooAKKKKACloooAWlpKUUgFopKKBBQaKDQAlJS0lAwNJRRSAKKSgUALS0lLTAWiiigBaSiigApKWkpAIaaadSUANoxS0UhiUUtGKYhKKXFFAhKKWkoAXNGaSigYtLTaWgBaWkpaYC5pKKKYBQaKQ0AFJSmkoASiiigBKSlopAJRRiigAooooAKWiimAUuKKKQxMUUtFADaKXFFMBMUUuKKACgUUtACiikpaBhRRRSGFFFJSAbikxTsUVRA2ilooASilpMUhhSUtFACUUtFACUtFLigApRSUtAhaWkpaBC0tNpaYC0UlLQAUUUtACUUtFAxKKXFGKQCUUtFACUUt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 truncated
"job_id": "4123b4ec-ea71-49f4-b7e2-ad86d622b947",
"grid_index": 0,
"is_alteration": false,
"order": 2,
"section_id": 0,
"request_id": "fc39cefd-fcf9-4775-b490-7e17ce670060",
"url": "https://imagine-public.x.ai/imagine-public/images/7693b927-1c34-488c-8ccc-6750367c66b1.png",
"id": "7693b927-1c34-488c-8ccc-6750367c66b1",
"prompt": "a dog typing on a keyboard",
"full_prompt": "a dog typing on a keyboard",
"percentage_complete": 50,
"model_name": "imagine_h_1",
"suggested_moderation_rule": null,
"r_rated": false
}
@@ -0,0 +1,25 @@
{
"type": "json",
"current_status": "completed",
"percentage_complete": 100,
"job_id": "c1bed796-3f05-4716-8ef0-a4d4900dfc6e",
"elapsed": null,
"prompt": "a dog typing on a keyboard",
"confirmation": "Drew a dog typing",
"full_prompt": "a dog typing on a keyboard",
"flags": null,
"is_alteration": false,
"section_id": 0,
"input_type": "text",
"request_id": "fc39cefd-fcf9-4775-b490-7e17ce670060",
"asr_transcript": null,
"asr_accepted": null,
"seed": null,
"image_reference": null,
"is_image_edit": null,
"moderated": false,
"r_rated": false,
"order": 1,
"model_name": "imagine_h_1",
"image_id": "78b3e6ef-defc-41a6-a9ac-b7a370d26ce5"
}
@@ -0,0 +1,25 @@
{
"type": "json",
"current_status": "start_stage",
"percentage_complete": 0,
"job_id": "57baeffe-d5cf-483b-a5bb-7794899f905c",
"elapsed": null,
"prompt": "a dog typing on a keyboard",
"confirmation": "Drew a dog typing",
"full_prompt": "a dog typing on a keyboard",
"flags": null,
"is_alteration": true,
"section_id": 0,
"input_type": "text",
"request_id": "fc39cefd-fcf9-4775-b490-7e17ce670060",
"asr_transcript": null,
"asr_accepted": null,
"seed": null,
"image_reference": null,
"is_image_edit": null,
"moderated": false,
"r_rated": null,
"order": 7,
"model_name": "imagine_h_1",
"image_id": null
}
@@ -0,0 +1,21 @@
{
"type": "conversation.item.create",
"timestamp": 1761010203630,
"item": {
"type": "message",
"content": [
{
"requestId": "fc39cefd-fcf9-4775-b490-7e17ce670060",
"text": "a dog typing on a keyboard",
"type": "input_text",
"properties": {
"section_count": 0,
"is_kids_mode": false,
"enable_nsfw": true,
"skip_upsampler": false,
"is_initial": false
}
}
]
}
}
@@ -0,0 +1,4 @@
{
"type": "other_type",
"foo": "bar"
}
@@ -0,0 +1 @@
{"type":"json","current_status":"start_stage","percentage_complete":0.0,"job_id":"0080eaac-f7f7-4369-90a2-346491939c1b","elapsed":null,"prompt":"a dog riding a motorcycle","confirmation":"Drew a dog riding a motorcycle","full_prompt":"a dog riding a motorcycle","flags":null,"is_alteration":false,"section_id":0,"input_type":"text","request_id":"bbc15402-3257-4415-922e-b156c151f3c2","asr_transcript":null,"asr_accepted":null,"seed":null,"image_reference":null,"is_image_edit":null,"moderated":false,"r_rated":null,"order":0,"model_name":"imagine_h_1","image_id":null}
@@ -0,0 +1,25 @@
{
"type": "json",
"current_status": "start_stage",
"percentage_complete": 0,
"job_id": "0080eaac-f7f7-4369-90a2-346491939c1b",
"elapsed": null,
"prompt": "a dog riding a motorcycle",
"confirmation": "Drew a dog riding a motorcycle",
"full_prompt": "a dog riding a motorcycle",
"flags": null,
"is_alteration": false,
"section_id": 0,
"input_type": "text",
"request_id": "bbc15402-3257-4415-922e-b156c151f3c2",
"asr_transcript": null,
"asr_accepted": null,
"seed": null,
"image_reference": null,
"is_image_edit": null,
"moderated": false,
"r_rated": null,
"order": 0,
"model_name": "imagine_h_1",
"image_id": null
}
@@ -0,0 +1,50 @@
use sqlx::pool::PoolConnection;
use sqlx::MySql;
use enums::common::payments_namespace::PaymentsNamespace;
use tokens::tokens::users::UserToken;
use crate::queries::users::user_stripe_customer_links::find_user_stripe_customer_link::UserStripeCustomerLink;
/// List every stripe customer link for a user, across all payments namespaces.
/// Users have at most one link per namespace.
pub async fn list_user_stripe_customer_links_for_user(
user_token: &UserToken,
connection: &mut PoolConnection<MySql>,
) -> Result<Vec<UserStripeCustomerLink>, sqlx::Error> {
let records = sqlx::query_as!(
RawUserStripeCustomerLink,
r#"
SELECT
user_token as `user_token: tokens::tokens::users::UserToken`,
payments_namespace as `payments_namespace: enums::common::payments_namespace::PaymentsNamespace`,
stripe_customer_id
FROM user_stripe_customer_links
WHERE user_token = ?
ORDER BY id ASC
"#,
user_token.as_str(),
)
.fetch_all(&mut **connection)
.await?;
let links = records
.into_iter()
.map(|record| UserStripeCustomerLink {
user_token: record.user_token,
payments_namespace: record.payments_namespace,
stripe_customer_id: record.stripe_customer_id,
})
.collect();
Ok(links)
}
#[derive(sqlx::FromRow)]
struct RawUserStripeCustomerLink {
user_token: UserToken,
payments_namespace: PaymentsNamespace,
stripe_customer_id: String,
}
@@ -1,2 +1,3 @@
pub mod find_user_stripe_customer_link;
pub mod upsert_user_stripe_customer_link;
pub mod list_user_stripe_customer_links_for_user;
pub mod upsert_user_stripe_customer_link;
@@ -0,0 +1,40 @@
use sqlx::pool::PoolConnection;
use sqlx::MySql;
use enums::common::payments_namespace::PaymentsNamespace;
use tokens::tokens::users::UserToken;
/// A distinct stripe customer id attached to one of the user's subscriptions.
pub struct UserSubscriptionStripeCustomerId {
pub subscription_namespace: PaymentsNamespace,
pub stripe_customer_id: String,
}
/// List the distinct stripe customer ids across all of a user's subscriptions
/// (current and historical), across all payments namespaces.
pub async fn list_stripe_customer_ids_for_user_subscriptions(
user_token: &UserToken,
connection: &mut PoolConnection<MySql>,
) -> Result<Vec<UserSubscriptionStripeCustomerId>, sqlx::Error> {
let records = sqlx::query_as!(
UserSubscriptionStripeCustomerId,
r#"
SELECT DISTINCT
subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,
maybe_stripe_customer_id as `stripe_customer_id!`
FROM user_subscriptions
WHERE
user_token = ?
AND maybe_stripe_customer_id IS NOT NULL
AND deleted_at IS NULL
ORDER BY subscription_namespace ASC, maybe_stripe_customer_id ASC
"#,
user_token.as_str(),
)
.fetch_all(&mut **connection)
.await?;
Ok(records)
}
@@ -1,7 +1,8 @@
pub mod find_possibly_inactive_first_subscription_for_owner_user;
pub mod lookup_users_by_stripe_customer_id;
pub mod find_subscription_for_owner_user;
pub mod get_user_subscription_by_stripe_subscription_id;
pub mod list_active_user_subscriptions;
pub mod list_stripe_customer_ids_for_user_subscriptions;
pub mod lookup_users_by_stripe_customer_id;
pub mod upsert_user_subscription_by_stripe_id;
pub mod upsert_user_subscription_with_invoice_paid_status_by_stripe_id;
@@ -117,6 +117,7 @@ use artcraft_api_defs::folders::media_files::*;
use artcraft_api_defs::folders::subfolder::*;
use artcraft_api_defs::moderation::user_referrals::list_global_user_referrals::*;
use artcraft_api_defs::moderation::user_referrals::list_user_referrals_for_user::*;
use artcraft_api_defs::moderation::user_stripe_data::moderator_get_user_stripe_customer_ids::*;
use crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::*;
use crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_handler::*;
use crate::http_server::endpoints::moderation::user_sessions::moderator_user_session_impersonation_request_handler::*;
@@ -558,6 +559,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
crate::http_server::endpoints::moderation::user_bans::moderation_ban_user_handler::moderation_ban_user_handler,
crate::http_server::endpoints::moderation::user_emails::moderator_change_user_email_handler::moderator_change_user_email_handler,
crate::http_server::endpoints::moderation::user_emails::moderator_list_email_address_changes_for_user_handler::moderator_list_email_address_changes_for_user_handler,
crate::http_server::endpoints::moderation::user_stripe_data::moderator_get_user_stripe_customer_ids_handler::moderator_get_user_stripe_customer_ids_handler,
// Folders
crate::http_server::endpoints::folders::folder::create_folder_handler::create_folder_handler,
@@ -816,6 +818,10 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
ModeratorGetWalletLedgerEntryPathInfo,
ModeratorGetWalletLedgerEntryResponse,
ModeratorGetWalletLedgerEntryDetails,
ModeratorGetUserStripeCustomerIdsPathInfo,
ModeratorGetUserStripeCustomerIdsResponse,
ModeratorUserStripeCustomerIdEntry,
ModeratorStripeCustomerIdSource,
GptImage1EditImageRequest,
GptImage1EditImageImageSize,
GptImage1EditImageNumImages,
@@ -10,5 +10,6 @@ pub mod user_emails;
pub mod user_feature_flags;
pub mod user_referrals;
pub mod user_sessions;
pub mod user_stripe_data;
pub mod wallet_ledger_entries;
pub mod wallets;
@@ -0,0 +1 @@
pub mod moderator_get_user_stripe_customer_ids_handler;
@@ -0,0 +1,85 @@
use std::sync::Arc;
use actix_web::web::{Json, Path};
use actix_web::{web, HttpRequest};
use log::warn;
use artcraft_api_defs::moderation::user_stripe_data::moderator_get_user_stripe_customer_ids::{
ModeratorGetUserStripeCustomerIdsPathInfo,
ModeratorGetUserStripeCustomerIdsResponse,
ModeratorStripeCustomerIdSource,
ModeratorUserStripeCustomerIdEntry,
};
use mysql_queries::queries::users::user_stripe_customer_links::list_user_stripe_customer_links_for_user::list_user_stripe_customer_links_for_user;
use mysql_queries::queries::users::user_subscriptions::list_stripe_customer_ids_for_user_subscriptions::list_stripe_customer_ids_for_user_subscriptions;
use tokens::tokens::users::UserToken;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, UseDatabase};
use crate::state::server_state::ServerState;
/// Get all stripe customer ids on file for a user (moderation)
#[utoipa::path(
get,
tag = "Moderation",
path = "/v1/moderation/user_stripe_data/{user_token}/customer_ids",
responses(
(status = 200, description = "Success", body = ModeratorGetUserStripeCustomerIdsResponse),
(status = 401, description = "Unauthorized"),
(status = 500, description = "Server error"),
),
params(
("user_token" = UserToken, Path, description = "User token to look up"),
)
)]
pub async fn moderator_get_user_stripe_customer_ids_handler(
http_request: HttpRequest,
path: Path<ModeratorGetUserStripeCustomerIdsPathInfo>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<Json<ModeratorGetUserStripeCustomerIdsResponse>, CommonWebError> {
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
let _moderator_session = require_moderator(
&http_request,
&server_state,
UseDatabase::FromPool(&mut mysql_connection),
)
.await
.map_err(|_| CommonWebError::NotAuthorized)?;
let customer_links = list_user_stripe_customer_links_for_user(&path.user_token, &mut mysql_connection)
.await
.map_err(|err| {
warn!("moderator_get_user_stripe_customer_ids customer link query error: {:?}", err);
CommonWebError::from_error(err)
})?;
let subscription_customer_ids = list_stripe_customer_ids_for_user_subscriptions(&path.user_token, &mut mysql_connection)
.await
.map_err(|err| {
warn!("moderator_get_user_stripe_customer_ids subscription query error: {:?}", err);
CommonWebError::from_error(err)
})?;
let mut customer_ids = Vec::with_capacity(customer_links.len() + subscription_customer_ids.len());
customer_ids.extend(customer_links.into_iter().map(|link| ModeratorUserStripeCustomerIdEntry {
stripe_customer_id: link.stripe_customer_id,
payments_namespace: link.payments_namespace,
source: ModeratorStripeCustomerIdSource::CustomerLink,
}));
customer_ids.extend(subscription_customer_ids.into_iter().map(|subscription| ModeratorUserStripeCustomerIdEntry {
stripe_customer_id: subscription.stripe_customer_id,
payments_namespace: subscription.subscription_namespace,
source: ModeratorStripeCustomerIdSource::Subscription,
}));
Ok(Json(ModeratorGetUserStripeCustomerIdsResponse {
success: true,
customer_ids,
}))
}
@@ -46,6 +46,7 @@ use crate::http_server::endpoints::moderation::user_referrals::moderator_list_gl
use crate::http_server::endpoints::moderation::user_referrals::moderator_list_user_referrals_for_user_handler::moderator_list_user_referrals_for_user_handler;
use crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_handler::moderator_list_user_session_impersonation_requests_handler;
use crate::http_server::endpoints::moderation::user_sessions::moderator_user_session_impersonation_request_handler::moderator_user_session_impersonation_request_handler;
use crate::http_server::endpoints::moderation::user_stripe_data::moderator_get_user_stripe_customer_ids_handler::moderator_get_user_stripe_customer_ids_handler;
pub fn add_moderator_routes<T, B> (app: App<T>) -> App<T>
where
@@ -171,6 +172,12 @@ pub fn add_moderator_routes<T, B> (app: App<T>) -> App<T>
.route(web::head().to(|| HttpResponse::Ok()))
)
)
.service(web::scope("/user_stripe_data")
.service(web::resource("/{user_token}/customer_ids")
.route(web::get().to(moderator_get_user_stripe_customer_ids_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
.service(web::resource("/wallet_ledger_entry/{wallet_ledger_entry_token}")
.route(web::get().to(moderator_get_wallet_ledger_entry_handler))
.route(web::head().to(|| HttpResponse::Ok()))