previously main.go handled auth directly, it connected to the database, ran migrations, created a JWT manager and SSHAuthService, and authenticated the SSH key before the TUI even started. this commit rips all of that out Auth now happens inside the TUI during the splash screen via splashAuthCmd, which calls the /auth/token endpoint. if no user exists or the fingerprint it auto-creates one using the SSH public ke, so there is no longer a separate registartion flow. The token and user com back in the response. once authed splashViewInitCmd fires a single /view/init request that loads everything needed to start the app, products, cart, addresses, cards, and orders, before switching to the shop page on the model side, User/*models.user and gossh.PublicKey were replaced with plain Fingerprint and SSHPublicKeyStr strings, and NewModelWithAuth was updated too
772 lines
21 KiB
Go
772 lines
21 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"terminalShop/pkg/models"
|
|
)
|
|
|
|
// Client handles communication with the API
|
|
type Client struct {
|
|
BaseURL string
|
|
HTTPClient *http.Client
|
|
Token string
|
|
}
|
|
|
|
// NewClient creates a new API client
|
|
func NewClient(baseURL string, token string) *Client {
|
|
return &Client{
|
|
BaseURL: baseURL,
|
|
Token: token,
|
|
HTTPClient: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// doRequest helper executes an HTTP request with the authorization header set
|
|
func (c *Client) doRequest(req *http.Request) (*http.Response, error) {
|
|
if c.Token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+c.Token)
|
|
}
|
|
return c.HTTPClient.Do(req)
|
|
}
|
|
|
|
// TokenRequest represents a request to exchange SSH fingerprint for JWT.
|
|
type TokenRequest struct {
|
|
Fingerprint string `json:"fingerprint"`
|
|
SSHPublicKey string `json:"ssh_public_key"`
|
|
ClientSecret string `json:"client_secret"`
|
|
}
|
|
|
|
// TokenResponse represents the JWT token response.
|
|
type TokenResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
User models.PublicUser `json:"user"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
func (c *Client) GetOrCreateToken(fingerprint, pubKeyStr, clientSecret string) (string, models.PublicUser, error) {
|
|
|
|
url := fmt.Sprintf("%s/api/v1/auth/token", c.BaseURL)
|
|
|
|
reqBody := TokenRequest{
|
|
Fingerprint: fingerprint,
|
|
ClientSecret: clientSecret,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return "", models.PublicUser{}, fmt.Errorf("failed to marshal token request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return "", models.PublicUser{}, fmt.Errorf("failed to create token request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return "", models.PublicUser{}, fmt.Errorf("token refresh request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", models.PublicUser{}, fmt.Errorf("token refresh returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var tokenResp TokenResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
|
return "", models.PublicUser{}, fmt.Errorf("failed to decode token response: %w", err)
|
|
}
|
|
|
|
if !tokenResp.Success || tokenResp.Data.AccessToken == "" {
|
|
return "", models.PublicUser{}, fmt.Errorf("token refresh failed: no access token in response")
|
|
}
|
|
|
|
return tokenResp.Data.AccessToken, tokenResp.Data.User, nil
|
|
}
|
|
|
|
func (c *Client) RefreshToken(fingerprint, clientSecret string) (string, error) {
|
|
token, _, err := c.GetOrCreateToken(fingerprint, "", clientSecret)
|
|
return token, err
|
|
}
|
|
|
|
// APIResponse represents the standard API response format
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Data map[string]interface{} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// APIError represents an error from the API
|
|
type APIError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Details map[string]interface{} `json:"details,omitempty"`
|
|
}
|
|
|
|
// ProductsResponse represents the products API response
|
|
type ProductsResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Products []models.Coffee `json:"products"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// GetProducts fetches all products from the API
|
|
func (c *Client) GetProducts() ([]models.Coffee, error) {
|
|
url := fmt.Sprintf("%s/api/v1/products", 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 products: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var productsResp ProductsResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&productsResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
if !productsResp.Success {
|
|
return nil, fmt.Errorf("API request was not successful")
|
|
}
|
|
|
|
return productsResp.Data.Products, nil
|
|
}
|
|
|
|
// Health checks if the API is healthy
|
|
func (c *Client) Health() error {
|
|
url := fmt.Sprintf("%s/api/v1/health", c.BaseURL)
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return fmt.Errorf("health check failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RegisterRequest represents a user registration request
|
|
type RegisterRequest struct {
|
|
Username string `json:"username"`
|
|
SSHPublicKey string `json:"ssh_public_key"`
|
|
SSHKeyFingerprint string `json:"ssh_key_fingerprint"`
|
|
}
|
|
|
|
// RegisterResponse represents the registration API response
|
|
type RegisterResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
User models.User `json:"user"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// RegisterUser registers a new user with SSH key authentication
|
|
func (c *Client) RegisterUser(username, sshPublicKey, sshKeyFingerprint string) (*models.User, error) {
|
|
url := fmt.Sprintf("%s/api/v1/auth/register", c.BaseURL)
|
|
|
|
reqBody := RegisterRequest{
|
|
Username: username,
|
|
SSHPublicKey: sshPublicKey,
|
|
SSHKeyFingerprint: sshKeyFingerprint,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to register user: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var registerResp RegisterResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(®isterResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
if !registerResp.Success {
|
|
if registerResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", registerResp.Error.Code, registerResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("registration failed")
|
|
}
|
|
|
|
return ®isterResp.Data.User, nil
|
|
}
|
|
|
|
// CartResponse represents the cart API response.
|
|
type CartResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Cart CartData `json:"cart"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// CartData represents the cart payload from the API.
|
|
type CartData struct {
|
|
Items []CartItemData `json:"items"`
|
|
Subtotal int `json:"subtotal"`
|
|
AddressID *uint `json:"address_id"`
|
|
CardID *uint `json:"card_id"`
|
|
ShippingCost int `json:"shipping_cost"`
|
|
ShippingService string `json:"shipping_service"`
|
|
}
|
|
|
|
// CartItemData represents a single item in the cart response.
|
|
type CartItemData struct {
|
|
ID uint `json:"id"`
|
|
CoffeeID uint `json:"coffee_id"`
|
|
Quantity int `json:"quantity"`
|
|
Subtotal int `json:"subtotal"`
|
|
Coffee models.Coffee `json:"coffee"`
|
|
}
|
|
|
|
// CardsResponse represents the cards list API response.
|
|
type CardsResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Cards []models.Card `json:"cards"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// CardResponse represents a single card API response.
|
|
type CardResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Card models.Card `json:"card"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// ConvertCartResponse represents the cart conversion (checkout) API response.
|
|
type ConvertCartResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Order models.Order `json:"order"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
// GetCart fetches the user's current cart.
|
|
func (c *Client) GetCart() (*CartData, error) {
|
|
url := fmt.Sprintf("%s/api/v1/cart", 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 cart: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var cartResp CartResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&cartResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode cart response: %w", err)
|
|
}
|
|
|
|
if !cartResp.Success {
|
|
if cartResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", cartResp.Error.Code, cartResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to fetch cart")
|
|
}
|
|
|
|
return &cartResp.Data.Cart, nil
|
|
}
|
|
|
|
// SetCartItem upserts an item in the cart. Quantity <= 0 removes it.
|
|
func (c *Client) SetCartItem(coffeeID uint, quantity int) (*CartData, error) {
|
|
url := fmt.Sprintf("%s/api/v1/cart/item", c.BaseURL)
|
|
|
|
body := map[string]interface{}{
|
|
"coffee_id": coffeeID,
|
|
"quantity": quantity,
|
|
}
|
|
jsonData, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set cart item: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var cartResp CartResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&cartResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode cart response: %w", err)
|
|
}
|
|
|
|
if !cartResp.Success {
|
|
if cartResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", cartResp.Error.Code, cartResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to set cart item")
|
|
}
|
|
|
|
return &cartResp.Data.Cart, nil
|
|
}
|
|
|
|
// SetCartAddress assigns a saved address to the cart.
|
|
func (c *Client) SetCartAddress(addressID uint) error {
|
|
url := fmt.Sprintf("%s/api/v1/cart/address", c.BaseURL)
|
|
|
|
body := map[string]interface{}{
|
|
"address_id": addressID,
|
|
}
|
|
jsonData, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set cart address: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result APIResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
if result.Error != nil {
|
|
return fmt.Errorf("%s: %s", result.Error.Code, result.Error.Message)
|
|
}
|
|
return fmt.Errorf("failed to set cart address")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetCartCard assigns a saved card to the cart.
|
|
func (c *Client) SetCartCard(cardID uint) error {
|
|
url := fmt.Sprintf("%s/api/v1/cart/card", c.BaseURL)
|
|
|
|
body := map[string]interface{}{
|
|
"card_id": cardID,
|
|
}
|
|
jsonData, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set cart card: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result APIResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
if result.Error != nil {
|
|
return fmt.Errorf("%s: %s", result.Error.Code, result.Error.Message)
|
|
}
|
|
return fmt.Errorf("failed to set cart card")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ClearCart removes all items from the user's cart.
|
|
func (c *Client) ClearCart() error {
|
|
url := fmt.Sprintf("%s/api/v1/cart", c.BaseURL)
|
|
|
|
req, err := http.NewRequest("DELETE", url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to clear cart: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result APIResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
if result.Error != nil {
|
|
return fmt.Errorf("%s: %s", result.Error.Code, result.Error.Message)
|
|
}
|
|
return fmt.Errorf("failed to clear cart")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConvertCart converts the cart into an order, charging the saved card.
|
|
func (c *Client) ConvertCart() (*models.Order, error) {
|
|
url := fmt.Sprintf("%s/api/v1/cart/convert", c.BaseURL)
|
|
|
|
req, err := http.NewRequest("POST", 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 convert cart: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var convertResp ConvertCartResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&convertResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode convert response: %w", err)
|
|
}
|
|
|
|
if !convertResp.Success {
|
|
if convertResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", convertResp.Error.Code, convertResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to convert cart")
|
|
}
|
|
|
|
return &convertResp.Data.Order, nil
|
|
}
|
|
|
|
// GetCards fetches all saved cards for the user.
|
|
func (c *Client) GetCards() ([]models.Card, error) {
|
|
url := fmt.Sprintf("%s/api/v1/cards", 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 cards: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var cardsResp CardsResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&cardsResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode cards response: %w", err)
|
|
}
|
|
|
|
if !cardsResp.Success {
|
|
if cardsResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", cardsResp.Error.Code, cardsResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to fetch cards")
|
|
}
|
|
|
|
return cardsResp.Data.Cards, nil
|
|
}
|
|
|
|
// SaveCard creates a saved card from a Stripe token.
|
|
func (c *Client) SaveCard(token string) (*models.Card, error) {
|
|
url := fmt.Sprintf("%s/api/v1/cards", c.BaseURL)
|
|
|
|
body := map[string]string{"token": token}
|
|
jsonData, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save card: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var cardResp CardResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&cardResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode card response: %w", err)
|
|
}
|
|
|
|
if !cardResp.Success {
|
|
if cardResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", cardResp.Error.Code, cardResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to save card")
|
|
}
|
|
|
|
return &cardResp.Data.Card, nil
|
|
}
|
|
|
|
// DeleteCard removes a saved card by ID.
|
|
func (c *Client) DeleteCard(id uint) error {
|
|
url := fmt.Sprintf("%s/api/v1/cards/%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 card: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("delete card failed with status %d", resp.StatusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type OrderResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Orders []models.Order `json:"orders"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
func (c *Client) GetOrders() ([]models.Order, error) {
|
|
url := fmt.Sprintf("%s/api/v1/orders", 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 orders: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var ordersResp OrderResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&ordersResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode orders response: %w", err)
|
|
}
|
|
|
|
if !ordersResp.Success {
|
|
if ordersResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", ordersResp.Error.Code, ordersResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to fetch orders")
|
|
}
|
|
return ordersResp.Data.Orders, nil
|
|
}
|
|
|
|
// The Addresses API response
|
|
type AddressResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Addresses []models.Address `json:"addresses"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
}
|
|
|
|
func (c *Client) GetAddresses() ([]models.Address, error) {
|
|
url := fmt.Sprintf("%s/api/v1/addresses", 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 addresses: %w", err)
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
var addrResp AddressResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&addrResp); err != nil {
|
|
return nil, fmt.Errorf("failed to decode addresses response: %w", err)
|
|
}
|
|
|
|
if !addrResp.Success {
|
|
if addrResp.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", addrResp.Error.Code, addrResp.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to fetch addresses")
|
|
}
|
|
|
|
return addrResp.Data.Addresses, nil
|
|
}
|
|
|
|
type CreateAddressRequest struct {
|
|
Name string `json:"name"`
|
|
Street string `json:"street"`
|
|
Street2 string `json:"street_2"`
|
|
City string `json:"city"`
|
|
State string `json:"state"`
|
|
Zip string `json:"zip"`
|
|
Country string `json:"country"`
|
|
Phone string `json:"phone"`
|
|
}
|
|
|
|
func (c *Client) CreateAddress(addrReq CreateAddressRequest) (*models.Address, error) {
|
|
url := fmt.Sprintf("%s/api/v1/addresses", c.BaseURL)
|
|
|
|
jsonData, err := json.Marshal(addrReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal address request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create address: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Address models.Address `json:"address"`
|
|
} `json:"data"`
|
|
Error *APIError `json:"error"`
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode address response: %w", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
if result.Error != nil {
|
|
return nil, fmt.Errorf("%s: %s", result.Error.Code, result.Error.Message)
|
|
}
|
|
return nil, fmt.Errorf("failed to create address")
|
|
}
|
|
|
|
return &result.Data.Address, nil
|
|
}
|
|
|
|
// Delete a saved address by ID
|
|
func (c *Client) DeleteAddress(id uint) error {
|
|
url := fmt.Sprintf("%s/api/v1/addresses/%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 address: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("delete address failed with status %d", resp.StatusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type ViewInitData struct {
|
|
User models.PublicUser `json:"user"`
|
|
Products []models.Coffee `json:"products"`
|
|
Cart []CartItemData `json:"cart"`
|
|
Addresses []models.Address `json:"addresses"`
|
|
Cards []models.Card `json:"cards"`
|
|
Orders []models.Order `json:"orders"`
|
|
}
|
|
|
|
type ViewInitResponse struct {
|
|
Success bool `json:"success"`
|
|
Data ViewInitData `json:"data"`
|
|
Error *APIError `json:"error"`
|
|
}
|
|
|
|
// GetViewInit fetches all initial data in one request
|
|
func (c *Client) GetViewInit() (ViewInitData, error) {
|
|
url := fmt.Sprintf("%s/api/v1/view/init", c.BaseURL)
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return ViewInitData{}, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
resp, err := c.doRequest(req)
|
|
if err != nil {
|
|
return ViewInitData{}, fmt.Errorf("view init request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return ViewInitData{}, fmt.Errorf("view init returned status: %d", resp.StatusCode)
|
|
}
|
|
var viewResp ViewInitResponse
|
|
|
|
return viewResp.Data, nil
|
|
}
|