280 lines
7.8 KiB
Go
280 lines
7.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func NewAccountCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "account",
|
|
Short: "Manage your TermCloud account",
|
|
Long: "Create, authenticate, and manage your TermCloud account",
|
|
}
|
|
|
|
cmd.AddCommand(newAccountCreateCmd())
|
|
cmd.AddCommand(newAccountLoginCmd())
|
|
cmd.AddCommand(newAccountInfoCmd())
|
|
cmd.AddCommand(newAccountTopUpCmd())
|
|
cmd.AddCommand(newAccountPaymentsCmd())
|
|
cmd.AddCommand(newAccountUsageCmd())
|
|
|
|
return cmd
|
|
}
|
|
|
|
func newAccountCreateCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a new TermCloud account",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
config, err := LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
client := NewClient(config.ServerURL, "")
|
|
ctx := context.Background()
|
|
|
|
account, err := client.CreateAccount(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create account: %w", err)
|
|
}
|
|
|
|
config.AccountNumber = account.AccountNumber
|
|
config.AccessToken = account.AccessToken
|
|
if err := config.Save(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Account created successfully!\n")
|
|
fmt.Printf("Account Number: %s\n", account.AccountNumber)
|
|
fmt.Printf("Access Token: %s\n", account.AccessToken)
|
|
fmt.Printf("Status: %s\n", getAccountStatus(account))
|
|
fmt.Printf("\nIMPORTANT: Save your account number and access token!\n")
|
|
fmt.Printf("You are now logged in. Add funds using: tcman account top-up 1\n")
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newAccountLoginCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "login <account-number> <access-token>",
|
|
Short: "Login to an existing account",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
accountNumber := args[0]
|
|
accessToken := args[1]
|
|
|
|
config, err := LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
client := NewClient(config.ServerURL, accessToken)
|
|
ctx := context.Background()
|
|
|
|
account, err := client.GetAccount(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to authenticate: %w", err)
|
|
}
|
|
|
|
if account.AccountNumber != accountNumber {
|
|
return fmt.Errorf("account number mismatch")
|
|
}
|
|
|
|
config.AccountNumber = accountNumber
|
|
config.AccessToken = accessToken
|
|
if err := config.Save(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Successfully logged in!\n")
|
|
fmt.Printf("Account: %s\n", account.AccountNumber)
|
|
fmt.Printf("Status: %s\n", getAccountStatus(account))
|
|
fmt.Printf("Balance: $%.2f USD\n", account.BalanceUSD)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newAccountInfoCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "info",
|
|
Short: "Show account information",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
config, err := LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
if !config.IsAuthenticated() {
|
|
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
|
|
}
|
|
|
|
client := NewClient(config.ServerURL, config.AccessToken)
|
|
ctx := context.Background()
|
|
|
|
account, err := client.GetAccount(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get account info: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Account Information:\n")
|
|
fmt.Printf(" Number: %s\n", account.AccountNumber)
|
|
fmt.Printf(" Status: %s\n", getAccountStatus(account))
|
|
fmt.Printf(" Balance: $%.2f USD\n", account.BalanceUSD)
|
|
fmt.Printf(" Created: %s\n", account.CreatedAt.Format("2006-01-02 15:04:05"))
|
|
if account.ActivatedAt != nil {
|
|
fmt.Printf(" Activated: %s\n", account.ActivatedAt.Format("2006-01-02 15:04:05"))
|
|
}
|
|
fmt.Printf(" Last Billing: %s\n", account.LastBillingDate.Format("2006-01-02"))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newAccountTopUpCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "top-up <amount> [account-number]",
|
|
Short: "Add funds to your account via Bitcoin",
|
|
Long: "Add funds to your account. If logged in, uses your account. If not logged in, provide account number.",
|
|
Args: cobra.RangeArgs(1, 2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
amount, err := strconv.ParseFloat(args[0], 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid amount: %w", err)
|
|
}
|
|
|
|
if amount < 1.0 {
|
|
return fmt.Errorf("minimum top-up amount is $1.00 USD")
|
|
}
|
|
|
|
config, err := LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
var accountNumber string
|
|
|
|
if len(args) == 2 {
|
|
accountNumber = args[1]
|
|
} else if config.IsAuthenticated() {
|
|
accountNumber = config.AccountNumber
|
|
} else if config.AccountNumber != "" {
|
|
accountNumber = config.AccountNumber
|
|
} else {
|
|
return fmt.Errorf("account number required. Usage: tcman account top-up <amount> <account-number>")
|
|
}
|
|
|
|
client := NewClient(config.ServerURL, "")
|
|
ctx := context.Background()
|
|
|
|
payment, err := client.CreatePaymentByAccountNumber(ctx, accountNumber, amount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create payment: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Bitcoin Payment Created:\n")
|
|
fmt.Printf(" Payment ID: %d\n", payment.ID)
|
|
fmt.Printf(" Account: %s\n", accountNumber)
|
|
fmt.Printf(" Amount: $%.2f USD (%.8f BTC)\n", payment.USDAmount, payment.BTCAmount)
|
|
fmt.Printf(" Address: %s\n", payment.BTCAddress)
|
|
fmt.Printf(" Status: %s\n", strings.ToUpper(payment.Status))
|
|
if payment.ExpiresAt != nil {
|
|
fmt.Printf(" Expires: %s\n", payment.ExpiresAt.Format("2006-01-02 15:04:05"))
|
|
}
|
|
fmt.Printf("\nSend exactly %.8f BTC to the address above.\n", payment.BTCAmount)
|
|
fmt.Printf("Your payment will be confirmed manually after Bitcoin is received.\n")
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newAccountPaymentsCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "payments",
|
|
Short: "List payment history",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
config, err := LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
if !config.IsAuthenticated() {
|
|
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
|
|
}
|
|
|
|
client := NewClient(config.ServerURL, config.AccessToken)
|
|
ctx := context.Background()
|
|
|
|
payments, err := client.GetPayments(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get payments: %w", err)
|
|
}
|
|
|
|
if len(payments) == 0 {
|
|
fmt.Printf("No payments found.\n")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
fmt.Fprintln(w, "ID\tAmount\tBTC Amount\tStatus\tCreated\tConfirmed")
|
|
fmt.Fprintln(w, "---\t------\t----------\t------\t-------\t---------")
|
|
|
|
for _, payment := range payments {
|
|
confirmedStr := "N/A"
|
|
if payment.ConfirmedAt != nil {
|
|
confirmedStr = payment.ConfirmedAt.Format("2006-01-02 15:04")
|
|
}
|
|
|
|
fmt.Fprintf(w, "%d\t$%.2f\t%.8f\t%s\t%s\t%s\n",
|
|
payment.ID,
|
|
payment.USDAmount,
|
|
payment.BTCAmount,
|
|
strings.ToUpper(payment.Status),
|
|
payment.CreatedAt.Format("2006-01-02 15:04"),
|
|
confirmedStr,
|
|
)
|
|
}
|
|
w.Flush()
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newAccountUsageCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "usage",
|
|
Short: "Show billing and usage history",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
fmt.Printf("Usage tracking is handled automatically.\n")
|
|
fmt.Printf("You are charged monthly based on peak storage usage.\n")
|
|
fmt.Printf("Current rate: $0.50 per GB per month.\n")
|
|
fmt.Printf("\nUse 'tcman account info' to see your current balance.\n")
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func getAccountStatus(account *Account) string {
|
|
if !account.IsActive {
|
|
return "INACTIVE (needs funds)"
|
|
}
|
|
if account.BalanceUSD <= 0 {
|
|
return "LOW BALANCE"
|
|
}
|
|
return "ACTIVE"
|
|
}
|