diff --git a/.env.include.example b/.env.include.example deleted file mode 100644 index b1094ef..0000000 --- a/.env.include.example +++ /dev/null @@ -1,2 +0,0 @@ -PORKBUN_API_KEY_NAME= -PORKBUN_SECRET_API_KEY_NAME= diff --git a/cmd/tui/main.go b/cmd/tui/main.go index 34197b0..5d6a73f 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -5,14 +5,11 @@ import ( "os" "git.juancwu.dev/juancwu/porkbacon/internal/app" - "git.juancwu.dev/juancwu/porkbacon/internal/config" tea "github.com/charmbracelet/bubbletea" ) func main() { - cfg := config.Load() - - a := app.New(cfg) + a := app.New() p := tea.NewProgram(a) if _, err := p.Run(); err != nil { diff --git a/go.mod b/go.mod index 4cc5845..beffffb 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module git.juancwu.dev/juancwu/porkbacon go 1.25.6 require ( + github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/joho/godotenv v1.5.1 ) require ( + github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect diff --git a/go.sum b/go.sum index 6bfe030..8770deb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= diff --git a/internal/app/app.go b/internal/app/app.go index 21065e5..498af70 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,33 +1,164 @@ package app import ( - "git.juancwu.dev/juancwu/porkbacon/internal/config" + "fmt" + + "git.juancwu.dev/juancwu/porkbacon/internal/pass" + "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) +const ( + StateInit uint = iota + StateRetrivePorkbunCredentials + StateIdle +) + type App struct { - cfg *config.Config + state uint + + apiKeyName string + secretApiKeyName string + apiKey string + secretKey string + loading bool + + textInput textinput.Model + spinner spinner.Model + + err error } -func New(cfg *config.Config) App { - return App{cfg: cfg} +func New() App { + ti := textinput.New() + ti.Placeholder = "Pass/Name" + ti.Focus() + ti.Width = 20 + + s := spinner.New() + s.Spinner = spinner.Dot + + return App{ + state: StateInit, + textInput: ti, + spinner: s, + } } func (a App) Init() tea.Cmd { - return nil + return tea.Sequence(textinput.Blink, a.spinner.Tick) } func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + var key string + switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c", "q": + case "ctrl+c": return a, tea.Quit + default: + key = msg.String() } } - return a, nil + + if a.err != nil { + return a, nil + } + + if a.state == StateInit && key == "enter" { + return a.handleEnter() + } + + if a.state == StateRetrivePorkbunCredentials && !a.loading { + return a.retrievePorkbunCredentials() + } + + a.textInput, cmd = a.textInput.Update(msg) + cmds = append(cmds, cmd) + + a.spinner, cmd = a.spinner.Update(msg) + cmds = append(cmds, cmd) + + return a, tea.Sequence(cmds...) } func (a App) View() string { - return "porkbacon app" + if a.err != nil { + return a.err.Error() + } + + if a.loading { + return fmt.Sprintf("%s Loading...", a.spinner.View()) + } + + switch a.state { + case StateInit: + return a.renderPorkbunCredentialForm() + case StateIdle: + return fmt.Sprintf("API key: %s\nSecret Key: %s\n\n", a.apiKey, a.secretKey) + } + return "Invalid state" +} + +func (a *App) renderPorkbunCredentialForm() string { + if a.apiKeyName == "" { + return fmt.Sprintf("Enter Porkbun API Key Name:\n%s\n\n", a.textInput.View()) + } + + if a.secretApiKeyName == "" { + 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) { + switch a.state { + case StateInit: + if a.apiKeyName == "" && a.textInput.Value() != "" { + 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 +} + +func (a *App) retrievePorkbunCredentials() (tea.Model, tea.Cmd) { + if a.apiKeyName == "" || a.secretApiKeyName == "" { + panic(fmt.Errorf("Porkbun credentials incomplete. Either API key or secret API key not defined.")) + } + + a.loading = true + + apiKey, err := pass.Get(a.apiKeyName) + if err != nil { + a.err = err + a.state = StateIdle + a.loading = false + return *a, nil + } + + secretKey, err := pass.Get(a.secretApiKeyName) + if err != nil { + a.err = err + a.state = StateIdle + a.loading = false + return *a, nil + } + + a.apiKey = apiKey + a.secretKey = secretKey + a.state = StateIdle + a.loading = false + + return *a, nil } diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index b03ecdf..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,33 +0,0 @@ -package config - -import ( - "fmt" - "os" - - "github.com/joho/godotenv" -) - -type Config struct { - PorkbunApiKeyName string - PorkbunSecretApiKeyName string -} - -func Load() *Config { - godotenv.Load() - - cfg := &Config{ - PorkbunApiKeyName: envRequired("PORKBUN_API_KEY_NAME"), - PorkbunSecretApiKeyName: envRequired("PORKBUN_SECRET_API_KEY_NAME"), - } - - return cfg -} - -func envRequired(key string) string { - value, ok := os.LookupEnv(key) - if !ok || value == "" { - fmt.Printf("Error: required environment variable %s not set.\n", key) - os.Exit(1) - } - return value -} diff --git a/internal/pass/pass.go b/internal/pass/pass.go new file mode 100644 index 0000000..cd5e4b4 --- /dev/null +++ b/internal/pass/pass.go @@ -0,0 +1,18 @@ +package pass + +import ( + "fmt" + "os/exec" + "strings" +) + +func Get(name string) (string, error) { + cmd := exec.Command("pass", "show", name) + + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("command execution failed: %w", err) + } + + return strings.TrimSpace(string(output)), nil +}