improve porkbun credentials loading

This commit is contained in:
juancwu 2026-01-21 19:28:31 +00:00
commit 4b92d2e7c4
3 changed files with 112 additions and 77 deletions

View file

@ -11,7 +11,7 @@ import (
func main() { func main() {
a := app.New() a := app.New()
p := tea.NewProgram(a) p := tea.NewProgram(&a)
if _, err := p.Run(); err != nil { if _, err := p.Run(); err != nil {
fmt.Println("Error:", err) fmt.Println("Error:", err)
os.Exit(1) os.Exit(1)

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"git.juancwu.dev/juancwu/porkbacon/internal/pass" "git.juancwu.dev/juancwu/porkbacon/internal/pass"
"git.juancwu.dev/juancwu/porkbacon/internal/porkbun"
"github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@ -11,18 +12,23 @@ import (
const ( const (
StateInit uint = iota StateInit uint = iota
StateRetrivePorkbunCredentials StateLoadingCredentials
StateIdle StateIdle
) )
type CredentialsLoadedMsg struct {
APIKey string
SecretKey string
}
type ErrMsg error
type App struct { type App struct {
state uint state uint
apiKeyName string apiKeyName string
secretApiKeyName string secretApiKeyName string
apiKey string client *porkbun.Client
secretKey string
loading bool
textInput textinput.Model textInput textinput.Model
spinner spinner.Model spinner spinner.Model
@ -32,7 +38,7 @@ type App struct {
func New() App { func New() App {
ti := textinput.New() ti := textinput.New()
ti.Placeholder = "Pass/Name" ti.Placeholder = "Pass Name"
ti.Focus() ti.Focus()
ti.Width = 20 ti.Width = 20
@ -50,115 +56,117 @@ func (a App) Init() tea.Cmd {
return tea.Sequence(textinput.Blink, a.spinner.Tick) return tea.Sequence(textinput.Blink, a.spinner.Tick)
} }
func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
var cmd tea.Cmd var cmd tea.Cmd
var key string
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
case "ctrl+c": case "ctrl+c":
return a, tea.Quit return a, tea.Quit
default: case "enter":
key = msg.String() if a.state == StateInit {
return a.handleEnter()
} }
} }
if a.err != nil { case CredentialsLoadedMsg:
a.client = porkbun.New(msg.APIKey, msg.SecretKey)
a.state = StateIdle
return a, nil
case ErrMsg:
a.err = msg
a.state = StateIdle // Or stay in error state
return a, nil return a, nil
} }
if a.state == StateInit && key == "enter" { if a.state == StateInit {
return a.handleEnter()
}
if a.state == StateRetrivePorkbunCredentials && !a.loading {
return a.retrievePorkbunCredentials()
}
a.textInput, cmd = a.textInput.Update(msg) a.textInput, cmd = a.textInput.Update(msg)
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
}
if a.state == StateLoadingCredentials {
a.spinner, cmd = a.spinner.Update(msg) a.spinner, cmd = a.spinner.Update(msg)
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
}
return a, tea.Sequence(cmds...) return a, tea.Sequence(cmds...)
} }
func (a App) View() string { func (a *App) View() string {
if a.err != nil { if a.err != nil {
return a.err.Error() return fmt.Sprintf("Error: %v\n\nPress Ctrl+C to quit.", a.err)
}
if a.loading {
return fmt.Sprintf("%s Loading...", a.spinner.View())
} }
switch a.state { switch a.state {
case StateInit: case StateInit:
return a.renderPorkbunCredentialForm() return a.renderPorkbunCredentialForm()
case StateLoadingCredentials:
return fmt.Sprintf("%s Loading credentials...", a.spinner.View())
case StateIdle: case StateIdle:
return fmt.Sprintf("API key: %s\nSecret Key: %s\n\n", a.apiKey, a.secretKey) return fmt.Sprintf("Ready!\nAPI Key: %s...\nSecret: %s...\n",
mask(a.client.APIKey), mask(a.client.SecretAPIKey))
} }
return "Invalid state" return "Invalid state"
} }
func mask(s string) string {
if len(s) > 4 {
return s[:4] + "...."
}
return "...."
}
func (a *App) renderPorkbunCredentialForm() string { func (a *App) renderPorkbunCredentialForm() string {
if a.apiKeyName == "" { title := "Enter Porkbun API Key Name (pass entry):"
return fmt.Sprintf("Enter Porkbun API Key Name:\n%s\n\n", a.textInput.View()) if a.apiKeyName != "" {
title = "Enter Porkbun Secret API Key Name (pass entry):"
} }
if a.secretApiKeyName == "" { return fmt.Sprintf("%s\n%s\n\n(Press Enter to confirm)", title, a.textInput.View())
return fmt.Sprintf("Enter Porkbun Secret API Key Name:\n%s\n\n", a.textInput.View())
}
return "Invalid state"
} }
func (a *App) handleEnter() (tea.Model, tea.Cmd) { func (a *App) handleEnter() (tea.Model, tea.Cmd) {
switch a.state { val := a.textInput.Value()
case StateInit: if val == "" {
if a.apiKeyName == "" && a.textInput.Value() != "" { return a, nil
a.apiKeyName = a.textInput.Value()
a.textInput.Reset()
} else if a.secretApiKeyName == "" && a.textInput.Value() != "" {
a.secretApiKeyName = a.textInput.Value()
a.textInput.Reset()
a.state = StateRetrivePorkbunCredentials
}
} }
return *a, nil if a.apiKeyName == "" {
a.apiKeyName = val
a.textInput.Reset()
a.textInput.Placeholder = "Secret Key Name"
return a, nil
}
if a.secretApiKeyName == "" {
a.secretApiKeyName = val
a.textInput.Reset()
a.state = StateLoadingCredentials
return a, retrievePorkbunCredentialsCmd(a.apiKeyName, a.secretApiKeyName)
}
return a, nil
} }
func (a *App) retrievePorkbunCredentials() (tea.Model, tea.Cmd) { func retrievePorkbunCredentialsCmd(apiKeyName, secretKeyName string) tea.Cmd {
if a.apiKeyName == "" || a.secretApiKeyName == "" { return func() tea.Msg {
panic(fmt.Errorf("Porkbun credentials incomplete. Either API key or secret API key not defined.")) apiKey, err := pass.Get(apiKeyName)
}
a.loading = true
apiKey, err := pass.Get(a.apiKeyName)
if err != nil { if err != nil {
a.err = err return ErrMsg(err)
a.state = StateIdle
a.loading = false
return *a, nil
} }
secretKey, err := pass.Get(a.secretApiKeyName) secretKey, err := pass.Get(secretKeyName)
if err != nil { if err != nil {
a.err = err return ErrMsg(err)
a.state = StateIdle
a.loading = false
return *a, nil
} }
a.apiKey = apiKey return CredentialsLoadedMsg{
a.secretKey = secretKey APIKey: apiKey,
a.state = StateIdle SecretKey: secretKey,
a.loading = false }
}
return *a, nil
} }

View file

@ -0,0 +1,27 @@
package porkbun
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const BaseURL = "https://api.porkbun.com/api/json/v3"
type Client struct {
APIKey string
SecretAPIKey string
HTTPClient *http.Client
}
func New(apiKey, secretKey string) *Client {
return &Client{
APIKey: apiKey,
SecretAPIKey: secretKey,
HTTPClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}