diff --git a/api/handlers/auth.go b/api/handlers/auth.go index acc09e3..1941ab5 100644 --- a/api/handlers/auth.go +++ b/api/handlers/auth.go @@ -83,34 +83,6 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) { return } - if req.SSHPublicKey != "" { - var sshKey models.SSHKey - // Use Unscoped to include soft-deleted rows, avoiding the - // UNIQUE constraint conflict when a key is re-registered. - err := db.Unscoped().Where("fingerprint = ?", req.Fingerprint).First(&sshKey).Error - if err != nil { - // Key doesn't exist at all — create it - sshKey = models.SSHKey{ - UserID: user.ID, - Fingerprint: req.Fingerprint, - PublicKey: req.SSHPublicKey, - } - if err := db.Create(&sshKey).Error; err != nil { - utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to save ssh key", nil) - return - } - } else if sshKey.DeletedAt.Valid { - // Key was soft-deleted — restore it - sshKey.DeletedAt = gorm.DeletedAt{} - sshKey.PublicKey = req.SSHPublicKey - sshKey.UserID = user.ID - if err := db.Unscoped().Save(&sshKey).Error; err != nil { - utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to restore ssh key", nil) - return - } - } - } - token, err := h.jwtManager.GenerateToken(user.ID, user.Email, user.Name) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "TOKEN_ERROR", "failed to generate token", nil) diff --git a/api/handlers/view.go b/api/handlers/view.go index 3245eb2..9727379 100644 --- a/api/handlers/view.go +++ b/api/handlers/view.go @@ -37,9 +37,6 @@ func (h *ViewHandler) GetViewInit(w http.ResponseWriter, r *http.Request) { var cards []models.Card db.Where("user_id = ?", userID).Find(&cards) - var sshKeys []models.SSHKey - db.Where("user_id = ?", userID).Order("created_at ASC").Find(&sshKeys) - var orders []models.Order db.Where("user_id = ?", userID).Preload("Items").Order("created_at desc").Find(&orders) var cartItems []models.CartItem @@ -53,7 +50,6 @@ func (h *ViewHandler) GetViewInit(w http.ResponseWriter, r *http.Request) { "cart": cartItems, "addresses": addresses, "cards": cards, - "ssh_keys": sshKeys, "orders": orders, }) } diff --git a/api/routes/routes.go b/api/routes/routes.go index bcc05c3..e57c00c 100644 --- a/api/routes/routes.go +++ b/api/routes/routes.go @@ -43,7 +43,6 @@ func SetupRoutes( addressHandler := handlers.NewAddressHandler(shippoAPIKey, bringAPIUID, bringAPIKey) viewHandler := handlers.NewViewHandler() webhookHandler := handlers.NewWebhookHandler(stripeWebhookSecret, stripeSecretKey) - sshKeyHandler := handlers.NewSSHKeyHandler() // Short payment redirect and success page — no auth required. r.Get("/pay/{token}", handlers.PayRedirect) @@ -115,14 +114,6 @@ func SetupRoutes( r.Delete("/{id}", addressHandler.DeleteAddress) r.Put("/{id}/default", addressHandler.SetDefaultAddress) }) - - // SSH Keys - r.Route("/ssh-keys", func(r chi.Router) { - // GET doesnt need id because it gets every ssh key - r.Get("/", sshKeyHandler.GetSSHKeys) - // Delete needs an id to delete only one key - r.Delete("/{id}", sshKeyHandler.DeleteSSHKeys) - }) }) }) diff --git a/pkg/api/client.go b/pkg/api/client.go index 8f6e55f..7898ae0 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -628,64 +628,6 @@ func (c *Client) DeleteCard(id uint) error { return nil } -type SSHKeyResponse struct { - Success bool `json:"success"` - Data struct { - SSHKeys []models.SSHKey `json:"ssh_keys"` - } `json:"data"` - Error *APIError `json:"error,omitempty"` -} - -func (c *Client) GetSSHKeys() ([]models.SSHKey, error) { - url := fmt.Sprintf("%s/api/v1/ssh-keys", c.BaseURL) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - - resp, err := c.doRequest(req) - if err != nil { - return nil, fmt.Errorf("failed to fetch ssh keys: %w", err) - } - defer resp.Body.Close() - - var keysResp SSHKeyResponse - if err := json.NewDecoder(resp.Body).Decode(&keysResp); err != nil { - return nil, fmt.Errorf("failed to decode ssh keys response: %w", err) - } - - if !keysResp.Success { - if keysResp.Error != nil { - return nil, fmt.Errorf("%s: %s", keysResp.Error.Code, keysResp.Error.Message) - } - return nil, fmt.Errorf("failed to fetch ssh keys") - } - return keysResp.Data.SSHKeys, nil - -} - -func (c *Client) DeleteSSHKey(id uint) error { - url := fmt.Sprintf("%s/api/v1/ssh-keys/%d", c.BaseURL, id) - - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return fmt.Errorf("failed to create delete request: %w", err) - } - - resp, err := c.doRequest(req) - if err != nil { - return fmt.Errorf("failed to delete ssh key: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("delete ssh key failed with status %d", resp.StatusCode) - } - - return nil -} - type OrderResponse struct { Success bool `json:"success"` Data struct { @@ -842,7 +784,6 @@ type ViewInitData struct { Cart []CartItemData `json:"cart"` Addresses []models.Address `json:"addresses"` Cards []models.Card `json:"cards"` - SSHKeys []models.SSHKey `json:"ssh_keys"` Orders []models.Order `json:"orders"` } diff --git a/pkg/database/migrate.go b/pkg/database/migrate.go index dc7b2a4..44a838e 100644 --- a/pkg/database/migrate.go +++ b/pkg/database/migrate.go @@ -23,7 +23,6 @@ func Migrate(db *gorm.DB) error { &models.Order{}, // Completed purchases &models.OrderItem{}, // Line items within orders &models.Address{}, // Saved addresses - &models.SSHKey{}, // One of the users ssh keys ); err != nil { return fmt.Errorf("migration failed: %w", err) } diff --git a/pkg/models/coffee.go b/pkg/models/coffee.go index 7ee5340..d8ec852 100644 --- a/pkg/models/coffee.go +++ b/pkg/models/coffee.go @@ -31,7 +31,6 @@ var AccountMenuItems = []string{ "order history", "addresses", "cards", - "ssh keys", "faq", "about", } diff --git a/pkg/tui/account.go b/pkg/tui/account.go index 0f6fde0..846a032 100644 --- a/pkg/tui/account.go +++ b/pkg/tui/account.go @@ -49,8 +49,6 @@ func (m Model) BuildAccountView(availableHeight int) string { detailView = m.AddressesView(detailContentWidth) case "cards": detailView = m.CardsView(detailContentWidth) - case "ssh keys": - detailView = m.SSHKeysView(detailContentWidth) case "faq": questionStyle := m.theme.TextHighlight().Bold(true) faqContent := "" @@ -222,10 +220,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { if m.account.cardCursor > 0 { m.account.cardCursor-- } - } else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil { - if m.account.sshKeyCursor > 0 { - m.account.sshKeyCursor-- - } } else if m.account.faqFocused { m.account.scrollOffset -= 3 if m.account.scrollOffset < 0 { @@ -255,10 +249,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { if m.account.cardCursor < len(m.SavedCards)-1 { m.account.cardCursor++ } - } else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil { - if m.account.sshKeyCursor < len(m.SSHKeys)-1 { - m.account.sshKeyCursor++ - } } else if m.account.faqFocused { m.account.scrollOffset += 3 maxScroll := m.computeFaqScrollMax() @@ -282,7 +272,7 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { if targetBottom > m.account.scrollOffset+viewportHeight { m.account.scrollOffset = targetBottom - viewportHeight } - } else if m.account.orderViewState == 0 && !m.account.addressListFocused && !m.account.cardListFocused && !m.account.sshKeyListFocused && m.account.cursor < len(models.AccountMenuItems)-1 { + } else if m.account.orderViewState == 0 && !m.account.addressListFocused && !m.account.cardListFocused && m.account.cursor < len(models.AccountMenuItems)-1 { m.account.cursor++ m.account.scrollOffset = 0 } @@ -309,8 +299,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { m.account.addressDeleting = &m.account.addressCursor } else if m.account.cardListFocused && m.account.cardDeleting == nil && m.account.cardCursor < len(m.SavedCards) { m.account.cardDeleting = &m.account.cardCursor - } else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil && m.account.sshKeyCursor < len(m.SSHKeys) { - m.account.sshKeyDeleting = &m.account.sshKeyCursor } case "y": @@ -332,21 +320,11 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { m.account.cardCursor-- } return m, m.deleteCardCmd(card.ID) - } else if m.account.sshKeyDeleting != nil { - cursor := *m.account.sshKeyDeleting - key := m.SSHKeys[cursor] - m.account.sshKeyDeleting = nil - m.SSHKeys = append(m.SSHKeys[:cursor], m.SSHKeys[cursor+1:]...) - if m.account.sshKeyCursor >= len(m.SSHKeys) && m.account.sshKeyCursor > 0 { - m.account.sshKeyCursor-- - } - return m, m.deleteSSHKeyCmd(key.ID) } case "n": m.account.addressDeleting = nil m.account.cardDeleting = nil - m.account.sshKeyDeleting = nil case "p", "enter": switch selectedItem { @@ -372,16 +350,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { } } } - case "ssh keys": - if !m.account.sshKeyListFocused { - m.account.sshKeyListFocused = true - m.account.sshKeyCursor = 0 - m.footer = []footerCommand{ - {key: "j/k", value: "keys"}, - {key: "x", value: "delete"}, - {key: "esc", value: "back"}, - } - } case "addresses": if !m.account.addressListFocused { m.account.addressListFocused = true @@ -437,11 +405,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) { m.account.cardListFocused = false m.account.cardCursor = 0 m.footer = accountDefaultFooter - } else if m.account.sshKeyDeleting != nil { - m.account.sshKeyDeleting = nil - } else if m.account.sshKeyListFocused { - m.account.sshKeyListFocused = false - m.account.sshKeyCursor = 0 m.footer = accountDefaultFooter } else if m.account.faqFocused { m.account.faqFocused = false diff --git a/pkg/tui/account_sshkeys.go b/pkg/tui/account_sshkeys.go deleted file mode 100644 index dbb6ba6..0000000 --- a/pkg/tui/account_sshkeys.go +++ /dev/null @@ -1,41 +0,0 @@ - -package tui - -func (m Model) SSHKeysView(width int) string { - titleSTyle := m.theme.TextAccent().Bold(true).MarginBottom(2) - contentStyle := m.theme.TextBody().Width(width) - activeStyle := m.theme.TextAccent().Bold(true) - - - lines := titleSTyle.Render("SSH keys") + "\n\n" - if !m.SSHKeysLoaded { - lines += contentStyle.Render("Loading ssh keys...") - } else if len(m.SSHKeys) == 0 { - lines += contentStyle.Render("No saved ssh keys") - } else { - for i, key := range m.SSHKeys { - isSelected := m.account.sshKeyListFocused && i == m.account.sshKeyCursor - label := key.Fingerprint - if key.Comment != "" { - label += " " + key.Comment - } - if len(label) > width-4 { - label += label[:width-4] - } - if m.account.sshKeyDeleting != nil && *m.account.sshKeyDeleting == i { - lines += m.theme.TextError().Bold(true).Render(" delete? (y/n)") + "\n" - } else if isSelected { - lines += activeStyle.Render("> "+label) + "\n" - } else { - lines += contentStyle.Render(" "+label) + "\n" - } - } - } - if m.account.sshKeyListFocused { - lines += "\n" + m.theme.TextDim().Render("x: delete esc: back") - } else { - lines += "\n" + m.theme.TextDim().Render("enter: manage") - } - - return lines -} diff --git a/pkg/tui/model.go b/pkg/tui/model.go index ebd4f2b..e120faa 100644 --- a/pkg/tui/model.go +++ b/pkg/tui/model.go @@ -77,8 +77,6 @@ type Model struct { SavedCards []models.Card Orders []models.Order OrdersLoaded bool - SSHKeys []models.SSHKey - SSHKeysLoaded bool FAQs []FAQ // Page navigation @@ -180,9 +178,6 @@ type accountState struct { cardCursor int addressDeleting *int cardDeleting *int - sshKeyListFocused bool - sshKeyCursor int - sshKeyDeleting *int } type reviewState struct { @@ -519,17 +514,6 @@ type CardDeletedMsg struct { Err error } -// SSHKeysMsg is sent when ssh keys are fetched from the API -type SSHKeysMsg struct { - Keys []models.SSHKey - Err error -} - -// SSHKeyDeletedMsg is sent when an ssh key is deleted -type SSHKeyDeletedMsg struct { - Err error -} - func (m Model) fetchAddressesCmd() tea.Cmd { return func() tea.Msg { if m.APIClient == nil || m.User == nil { @@ -592,26 +576,6 @@ func (m Model) deleteCardCmd(id uint) tea.Cmd { } } -func (m Model) fetchSSHKeysCmd() tea.Cmd { - return func() tea.Msg { - if m.APIClient == nil || m.User == nil { - return SSHKeysMsg{Err: fmt.Errorf("not authenticated")} - } - keys, err := m.APIClient.GetSSHKeys() - return SSHKeysMsg{Keys: keys, Err: err} - } -} - -func (m Model) deleteSSHKeyCmd(id uint) tea.Cmd { - return func() tea.Msg { - if m.APIClient == nil || m.User == nil { - return SSHKeyDeletedMsg{Err: fmt.Errorf("not authenticated")} - } - err := m.APIClient.DeleteSSHKey(id) - return SSHKeyDeletedMsg{Err: err} - } -} - // checkoutWithSavedCard sets address and saved card on the cart, then converts. // Used when the user selects an existing card from the list (no tokenization needed). func (m Model) checkoutWithSavedCard() tea.Cmd { diff --git a/pkg/tui/update.go b/pkg/tui/update.go index b302e85..5cbd56d 100644 --- a/pkg/tui/update.go +++ b/pkg/tui/update.go @@ -89,8 +89,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.loadCartFromAPI(cart) m.SavedAddresses = msg.Data.Addresses m.SavedCards = msg.Data.Cards - m.SSHKeys = msg.Data.SSHKeys - m.SSHKeysLoaded = true m.Orders = msg.Data.Orders m.OrdersLoaded = true m.splash.dataReady = true @@ -133,17 +131,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.ErrorMsg = fmt.Sprintf("failed to delete card: %v", msg.Err) } return m, nil - case SSHKeysMsg: - if msg.Err == nil { - m.SSHKeys = msg.Keys - m.SSHKeysLoaded = true - } - return m, nil - case SSHKeyDeletedMsg: - if msg.Err != nil { - m.ErrorMsg = fmt.Sprintf("failed to delete ssh key: %v", msg.Err) - } - return m, nil case DelayCompleteMsg: m.splash.delayDone = true if m.currentPage == splashPage && m.splash.dataReady {