list domains

This commit is contained in:
juancwu 2026-01-23 14:56:16 +00:00
commit 8498f6f0a7
4 changed files with 146 additions and 10 deletions

View file

@ -26,6 +26,28 @@ func New(apiKey, secretKey string) *Client {
}
}
// Get all domain names in account. Domains are returned in chunks of 1000.
func (c *Client) DomainListAll(start int, includeLabels bool) (*DomainListAllResponse, error) {
reqBody := DomainListAllRequest{
BaseRequest: BaseRequest{
APIKey: c.APIKey,
SecretAPIKey: c.SecretAPIKey,
},
Start: start,
IncludeLabels: "no",
}
if includeLabels {
reqBody.IncludeLabels = "yes"
}
var resp DomainListAllResponse
err := c.post("/domain/listAll", reqBody, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
func (c *Client) Ping() (*PingResponse, error) {
reqBody := PingRequest{
BaseRequest: BaseRequest{
@ -43,7 +65,7 @@ func (c *Client) Ping() (*PingResponse, error) {
return &resp, nil
}
func (c *Client) post(endpoint string, body interface{}, target interface{}) error {
func (c *Client) post(endpoint string, body any, target any) error {
jsonBody, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("failed to marshal request body: %w", err)

View file

@ -22,3 +22,37 @@ type PingResponse struct {
BaseResponse
YourIP string `json:"yourIp"`
}
// DomainListAllRequest is used for getting a list of all domains.
type DomainListAllRequest struct {
BaseRequest
Start int `json:"start"`
IncludeLabels string `json:"includeLabels,omitempty"`
}
// DomainListAllResponse is the response from the domain list all endpoint.
type DomainListAllResponse struct {
BaseResponse
Domains []Domain `json:"domains"`
}
// Domain represents a single domain returned by domain endpoints.
type Domain struct {
Domain string `json:"domain"`
Status string `json:"status"`
TLD string `json:"tld"`
CreateDate string `json:"createDate"`
ExpireDate string `json:"expireDate"`
SecurityLock string `json:"securityLock"`
WhoIsPrivacy string `json:"whoisPrivacy"`
AutoRenew any `json:"autoRenew"`
NotLocal any `json:"notLocal"`
Labels []DomainLabel `json:"labels,omitempty"`
}
// DomainLabel represents a label associated with a domain.
type DomainLabel struct {
ID string `json:"id"`
Title string `json:"title"`
Color string `json:"color"`
}

View file

@ -0,0 +1,30 @@
package menu
import (
"fmt"
"strings"
"git.juancwu.dev/juancwu/porkbacon/internal/porkbun"
)
func renderDomainItem(item *porkbun.Domain) string {
var b strings.Builder
b.WriteString("Domain: " + item.Domain + "\n")
b.WriteString("Status: " + item.Status + "\n")
b.WriteString("Create Date: " + item.CreateDate + "\n")
b.WriteString("Expire Date: " + item.ExpireDate + "\n")
b.WriteString("Security Lock: " + item.SecurityLock + "\n")
b.WriteString("Whois Privacy: " + item.WhoIsPrivacy + "\n")
b.WriteString(fmt.Sprintln("Auto Renew:", item.AutoRenew))
b.WriteString(fmt.Sprintln("Not Local:", item.NotLocal))
if len(item.Labels) > 0 {
b.WriteString("Labels:\n")
}
for i, label := range item.Labels {
b.WriteString("=> " + label.Title)
if i < len(item.Labels)-1 {
b.WriteString("\n")
}
}
return b.String()
}

View file

@ -6,13 +6,20 @@ import (
"git.juancwu.dev/juancwu/porkbacon/internal/porkbun"
"git.juancwu.dev/juancwu/porkbacon/internal/ui/messages"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/paginator"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type pingResultMsg struct {
IP string
}
type domainListMsg struct {
Status string
Domains []string
}
type menuItem struct {
id uint8
title, desc string
@ -25,7 +32,6 @@ func (i menuItem) FilterValue() string { return i.title }
const (
domainListAll uint8 = iota
domainGetDetails
dnsRetrieveRecords
dnsCreateRecord
dnsEditRecord
@ -35,6 +41,8 @@ const (
type Model struct {
list list.Model
paginator paginator.Model
domains []string
client *porkbun.Client
err error
output string
@ -43,7 +51,6 @@ type Model struct {
func New(client *porkbun.Client) *Model {
items := []list.Item{
menuItem{id: domainListAll, title: "Domain: List All", desc: "List all domains in your account"},
menuItem{id: domainGetDetails, title: "Domain: Get Details", desc: "Get details for a specific domain"},
menuItem{id: dnsRetrieveRecords, title: "DNS: Retrieve Records", desc: "Retrieve DNS records for a domain"},
menuItem{id: dnsCreateRecord, title: "DNS: Create Record", desc: "Create a new DNS record"},
menuItem{id: dnsEditRecord, title: "DNS: Edit Record", desc: "Edit an existing DNS record"},
@ -54,8 +61,15 @@ func New(client *porkbun.Client) *Model {
l := list.New(items, list.NewDefaultDelegate(), 0, 0)
l.Title = "Porkbun Actions"
p := paginator.New()
p.Type = paginator.Dots
p.PerPage = 1
p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•")
p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•")
return &Model{
list: l,
paginator: p,
client: client,
}
}
@ -80,6 +94,16 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
if len(m.domains) > 0 {
var cmd tea.Cmd
m.paginator, cmd = m.paginator.Update(msg)
if msg.String() == "esc" {
m.domains = nil
return m, nil
}
return m, cmd
}
if msg.String() == "enter" {
i, ok := m.list.SelectedItem().(menuItem)
if ok {
@ -87,6 +111,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
case domainListMsg:
m.paginator.PerPage = 1
m.paginator.SetTotalPages(len(msg.Domains))
m.paginator.Page = 0
m.domains = msg.Domains
case pingResultMsg:
m.output = fmt.Sprintf("Ping successful!\nYour IP: %s", msg.IP)
return m, nil
@ -105,12 +135,32 @@ func (m *Model) View() string {
if m.output != "" {
return fmt.Sprintf("%s\n\n(Press Esc to go back)", m.output)
}
if len(m.domains) > 0 {
return fmt.Sprintf("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.domains[m.paginator.Page], m.paginator.View())
}
return m.list.View()
}
func (m *Model) handleSelection(i menuItem) (tea.Model, tea.Cmd) {
// TODO: Implement other actions. For now, just Ping.
switch i.id {
case domainListAll:
return m, func() tea.Msg {
resp, err := m.client.DomainListAll(0, true)
if err != nil {
return messages.ErrorMsg(err)
}
var domains []string
for _, domain := range resp.Domains {
view := renderDomainItem(&domain)
domains = append(domains, view)
}
return domainListMsg{
Status: resp.Status,
Domains: domains,
}
}
case utilPing:
return m, func() tea.Msg {
resp, err := m.client.Ping()