From 9e71117963941e5809574f07ad381fb3e9573e4e Mon Sep 17 00:00:00 2001 From: Luca Steeb Date: Thu, 9 Jan 2025 16:07:39 +0000 Subject: [PATCH] feat(auth): add email and billing management to CLI (#155) Introduced commands for setting email and enabling billing. Updated GraphQL schema to support these actions. --- cli/src/command/auth/billing.rs | 29 ++ cli/src/command/auth/email.rs | 28 ++ cli/src/command/auth/mod.rs | 16 +- slot/schema.json | 454 ++++++++++++++++++++++++ slot/src/graphql/auth/mod.rs | 8 + slot/src/graphql/auth/update-me.graphql | 11 + 6 files changed, 544 insertions(+), 2 deletions(-) create mode 100644 cli/src/command/auth/billing.rs create mode 100644 cli/src/command/auth/email.rs create mode 100644 slot/src/graphql/auth/update-me.graphql diff --git a/cli/src/command/auth/billing.rs b/cli/src/command/auth/billing.rs new file mode 100644 index 0000000..b33cd2b --- /dev/null +++ b/cli/src/command/auth/billing.rs @@ -0,0 +1,29 @@ +use anyhow::Result; +use clap::Args; +use slot::graphql::auth::{update_me::*, UpdateMe}; +use slot::graphql::GraphQLQuery; +use slot::{api::Client, credential::Credentials}; + +#[derive(Debug, Args)] +#[command(next_help_heading = "Set billing options")] +pub struct BillingArgs { + #[arg(help = "Enable slot billing for the authenticated user.")] + #[clap(long, short, action)] + pub enabled: bool, +} + +impl BillingArgs { + pub async fn run(&self) -> Result<()> { + let credentials = Credentials::load()?; + let client = Client::new_with_token(credentials.access_token); + + let request_body = UpdateMe::build_query(Variables { + email: None, + slot_billing: Some(self.enabled), + }); + let res: ResponseData = client.query(&request_body).await?; + print!("{:?}", res); + + Ok(()) + } +} diff --git a/cli/src/command/auth/email.rs b/cli/src/command/auth/email.rs new file mode 100644 index 0000000..b39836d --- /dev/null +++ b/cli/src/command/auth/email.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use clap::Args; +use slot::graphql::auth::{update_me::*, UpdateMe}; +use slot::graphql::GraphQLQuery; +use slot::{api::Client, credential::Credentials}; + +#[derive(Debug, Args)] +#[command(next_help_heading = "Set email options")] +pub struct EmailArgs { + #[arg(help = "The email address of the user.")] + pub email: String, +} + +impl EmailArgs { + pub async fn run(&self) -> Result<()> { + let credentials = Credentials::load()?; + let client = Client::new_with_token(credentials.access_token); + + let request_body = UpdateMe::build_query(Variables { + email: Some(self.email.clone()), + slot_billing: None, + }); + let res: ResponseData = client.query(&request_body).await?; + print!("{:?}", res); + + Ok(()) + } +} diff --git a/cli/src/command/auth/mod.rs b/cli/src/command/auth/mod.rs index a67e665..5bb4c7f 100644 --- a/cli/src/command/auth/mod.rs +++ b/cli/src/command/auth/mod.rs @@ -1,8 +1,10 @@ +use self::{email::EmailArgs, info::InfoArgs, login::LoginArgs}; +use crate::command::auth::billing::BillingArgs; use anyhow::Result; use clap::Subcommand; -use self::{info::InfoArgs, login::LoginArgs}; - +mod billing; +mod email; mod info; mod login; mod session; @@ -11,8 +13,16 @@ mod session; pub enum Auth { #[command(about = "Login to your Cartridge account.")] Login(LoginArgs), + #[command(about = "Display info about the authenticated user.")] Info(InfoArgs), + + #[command(about = "Set the email address for the authenticated user.")] + SetEmail(EmailArgs), + + #[command(about = "Manage slot billing for the authenticated user.")] + EnableSlotBilling(BillingArgs), + // Mostly for testing purposes, will eventually turn it into a library call from `sozo`. #[command(hide = true)] CreateSession(session::CreateSession), @@ -24,6 +34,8 @@ impl Auth { Auth::Login(args) => args.run().await, Auth::Info(args) => args.run().await, Auth::CreateSession(args) => args.run().await, + Auth::SetEmail(args) => args.run().await, + Auth::EnableSlotBilling(args) => args.run().await, } } } diff --git a/slot/schema.json b/slot/schema.json index 4efad4c..28f699a 100644 --- a/slot/schema.json +++ b/slot/schema.json @@ -279,6 +279,34 @@ } } }, + { + "args": [], + "deprecationReason": null, + "description": "If true, the account is billed for paid slot deployments", + "isDeprecated": false, + "name": "slotBilling", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": "Optional email for account, required for slot billing", + "isDeprecated": false, + "name": "email", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, { "args": [], "deprecationReason": null, @@ -1235,6 +1263,37 @@ "name": "AccountTeamWhereInput", "possibleTypes": [] }, + { + "description": null, + "enumValues": [], + "fields": [], + "inputFields": [ + { + "defaultValue": null, + "description": "Enable slot billing for the account. Requires email to be set.", + "name": "slotBilling", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": "Set the email for the account. Required for slot billing.", + "name": "email", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ], + "interfaces": [], + "kind": "INPUT_OBJECT", + "name": "AccountUpdateInput", + "possibleTypes": [] + }, { "description": "AccountWhereInput is used for filtering Account objects.\nInput was generated by ent.", "enumValues": [], @@ -1694,6 +1753,192 @@ "ofType": null } }, + { + "defaultValue": null, + "description": "slot_billing field predicates", + "name": "slotBilling", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "slotBillingNEQ", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": "email field predicates", + "name": "email", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailNEQ", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailIn", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailNotIn", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailGT", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailGTE", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailLT", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailLTE", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailContains", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailHasPrefix", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailHasSuffix", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailIsNil", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailNotNil", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailEqualFold", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "emailContainsFold", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, { "defaultValue": null, "description": "created_at field predicates", @@ -6129,6 +6374,18 @@ "ofType": null } }, + { + "args": [], + "deprecationReason": null, + "description": "Optional comment for transaction reason", + "isDeprecated": false, + "name": "comment", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, { "args": [], "deprecationReason": null, @@ -6780,6 +7037,172 @@ "ofType": null } }, + { + "defaultValue": null, + "description": "comment field predicates", + "name": "comment", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentNEQ", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentIn", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentNotIn", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentGT", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentGTE", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentLT", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentLTE", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentContains", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentHasPrefix", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentHasSuffix", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentIsNil", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentNotNil", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentEqualFold", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "commentContainsFold", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, { "defaultValue": null, "description": "created_at field predicates", @@ -11583,6 +12006,37 @@ } } }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "data", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "AccountUpdateInput", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "updateMe", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Account", + "ofType": null + } + } + }, { "args": [ { diff --git a/slot/src/graphql/auth/mod.rs b/slot/src/graphql/auth/mod.rs index 4ce0af6..eb1e88f 100644 --- a/slot/src/graphql/auth/mod.rs +++ b/slot/src/graphql/auth/mod.rs @@ -14,6 +14,14 @@ use crate::account::{self}; )] pub struct Me; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "schema.json", + query_path = "src/graphql/auth/update-me.graphql", + response_derives = "Debug, Clone, Serialize, PartialEq, Eq" +)] +pub struct UpdateMe; + impl From for account::AccountInfo { fn from(value: MeMe) -> Self { let id = value.id; diff --git a/slot/src/graphql/auth/update-me.graphql b/slot/src/graphql/auth/update-me.graphql new file mode 100644 index 0000000..e1015cb --- /dev/null +++ b/slot/src/graphql/auth/update-me.graphql @@ -0,0 +1,11 @@ +mutation UpdateMe( + $email: String + $slotBilling: Boolean +) { + updateMe(data: { + slotBilling: $slotBilling + email: $email + }) { + id + } +}