225 lines
6.7 KiB
Go
225 lines
6.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"terminalShop/api/middleware"
|
|
"terminalShop/pkg/bring"
|
|
"terminalShop/pkg/database"
|
|
"terminalShop/pkg/models"
|
|
"terminalShop/pkg/shippo"
|
|
"terminalShop/pkg/utils"
|
|
)
|
|
|
|
func addressLog() *slog.Logger { return slog.With("component", "address") }
|
|
|
|
type AddressHandler struct {
|
|
shippoKey string
|
|
bringAPIUID string
|
|
bringAPIKey string
|
|
}
|
|
|
|
// NewAddressHandler creates a new AddressHandler with address validation API keys.
|
|
func NewAddressHandler(shippoAPIKey, bringAPIUID, bringAPIKey string) *AddressHandler {
|
|
return &AddressHandler{
|
|
shippoKey: shippoAPIKey,
|
|
bringAPIUID: bringAPIUID,
|
|
bringAPIKey: bringAPIKey,
|
|
}
|
|
}
|
|
|
|
// Retrieve all saved addresses for a user
|
|
func (h *AddressHandler) GetAddresses(w http.ResponseWriter, r *http.Request) {
|
|
db := database.GetDB().WithContext(r.Context())
|
|
|
|
userID := middleware.UserIDFromContext(r.Context())
|
|
|
|
var addresses []models.Address
|
|
db.Where("user_id = ?", userID).Order("is_default DESC, created_at DESC").Find(&addresses)
|
|
|
|
utils.RespondSuccess(w, http.StatusOK, map[string]any{
|
|
"addresses": addresses,
|
|
})
|
|
}
|
|
|
|
func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) {
|
|
db := database.GetDB().WithContext(r.Context())
|
|
|
|
userID := middleware.UserIDFromContext(r.Context())
|
|
|
|
var req 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"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
utils.RespondError(w, http.StatusBadRequest, "INVALID_JSON", "invalid request body", nil)
|
|
return
|
|
}
|
|
|
|
if req.Name == "" || req.Street == "" || req.City == "" || req.Zip == "" || req.Country == "" {
|
|
utils.RespondError(w, http.StatusBadRequest, "VALIDATION_ERROR", "name, street, city, zip, and country are required", nil)
|
|
return
|
|
}
|
|
|
|
// Validate and normalize address via country-specific provider
|
|
var address models.Address
|
|
switch req.Country {
|
|
case "US":
|
|
if h.shippoKey == "" {
|
|
utils.RespondError(w, http.StatusInternalServerError, "CONFIG_ERROR", "shippo not configured for US address validation", nil)
|
|
return
|
|
}
|
|
client := shippo.NewClient(h.shippoKey)
|
|
validated, err := client.ValidateAddress(r.Context(), shippo.Address{
|
|
Name: req.Name,
|
|
Street1: req.Street,
|
|
Street2: req.Street2,
|
|
City: req.City,
|
|
State: req.State,
|
|
Country: req.Country,
|
|
Zip: req.Zip,
|
|
Phone: req.Phone,
|
|
})
|
|
if err != nil {
|
|
addressLog().Warn("shippo address validation failed", "error", err, "country", "US")
|
|
utils.RespondError(w, http.StatusBadRequest, "INVALID_ADDRESS", "address is invalid", nil)
|
|
return
|
|
}
|
|
address = models.Address{
|
|
UserID: userID,
|
|
Name: validated.Name,
|
|
Street: validated.Street1,
|
|
Street2: validated.Street2,
|
|
City: validated.City,
|
|
State: validated.State,
|
|
Zip: validated.Zip,
|
|
Country: validated.Country,
|
|
Phone: validated.Phone,
|
|
}
|
|
case "NO":
|
|
if h.bringAPIUID == "" || h.bringAPIKey == "" {
|
|
utils.RespondError(w, http.StatusBadRequest, "CONFIG_ERROR", "bring not configured for Norwegian address validation", nil)
|
|
return
|
|
}
|
|
client := bring.NewClient(h.bringAPIUID, h.bringAPIKey)
|
|
validated, err := client.ValidateAddress(r.Context(), bring.Address{
|
|
Name: req.Name,
|
|
Street1: req.Street,
|
|
Street2: req.Street2,
|
|
City: req.City,
|
|
Zip: req.Zip,
|
|
Country: req.Country,
|
|
Phone: req.Phone,
|
|
})
|
|
if err != nil {
|
|
addressLog().Warn("bring address validation failed", "error", err, "country", "NO")
|
|
utils.RespondError(w, http.StatusBadRequest, "INVALID_ADDRESS", "address is invalid", nil)
|
|
return
|
|
}
|
|
address = models.Address{
|
|
UserID: userID,
|
|
Name: validated.Name,
|
|
Street: validated.Street1,
|
|
Street2: validated.Street2,
|
|
City: validated.City,
|
|
Zip: validated.Zip,
|
|
Country: validated.Country,
|
|
Phone: validated.Phone,
|
|
}
|
|
default:
|
|
utils.RespondError(w, http.StatusBadRequest, "UNSUPPORTED_COUNTRY", "address validation is not supported for this country", nil)
|
|
return
|
|
}
|
|
|
|
// remove existing addres with the same street addres
|
|
var existing models.Address
|
|
if err := db.Where("user_id = ? AND street = ?", userID, address.Street).First(&existing).Error; err == nil {
|
|
db.Delete(&existing)
|
|
}
|
|
|
|
if err := db.Create(&address).Error; err != nil {
|
|
utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to create address", nil)
|
|
return
|
|
}
|
|
|
|
utils.RespondSuccess(w, http.StatusCreated, map[string]any{
|
|
"address": address,
|
|
})
|
|
}
|
|
|
|
// DeleteAddress deltes a saved address
|
|
func (h *AddressHandler) DeleteAddress(w http.ResponseWriter, r *http.Request) {
|
|
db := database.GetDB().WithContext(r.Context())
|
|
userID := middleware.UserIDFromContext(r.Context())
|
|
|
|
idStr := chi.URLParam(r, "id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
utils.RespondError(w, http.StatusBadRequest, "INVALID_ID", "invalid address id", nil)
|
|
return
|
|
}
|
|
|
|
var address models.Address
|
|
|
|
if err := db.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil {
|
|
utils.RespondError(w, http.StatusNotFound, "ADDRESS_NOT_FOUND", "address not found", nil)
|
|
return
|
|
}
|
|
|
|
if err := db.Delete(&address).Error; err != nil {
|
|
utils.RespondError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to delete address", nil)
|
|
return
|
|
}
|
|
|
|
utils.RespondSuccess(w, http.StatusOK, map[string]any{
|
|
"message": "address successfully deleted",
|
|
})
|
|
}
|
|
|
|
// Mark an address as the users default address
|
|
|
|
func (h *AddressHandler) SetDefaultAddress(w http.ResponseWriter, r *http.Request) {
|
|
db := database.GetDB().WithContext(r.Context())
|
|
userID := middleware.UserIDFromContext(r.Context())
|
|
|
|
idStr := chi.URLParam(r, "id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
utils.RespondError(w, http.StatusBadRequest, "INVALID_ID", "invalid address id", nil)
|
|
return
|
|
}
|
|
|
|
var address models.Address
|
|
if err := db.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil {
|
|
utils.RespondError(w, http.StatusNotFound, "ADDRESS_NOT_FOUND", "address not found", nil)
|
|
return
|
|
}
|
|
|
|
if err := db.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false).Error; err != nil {
|
|
utils.RespondError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to update address", nil)
|
|
return
|
|
}
|
|
|
|
address.IsDefault = true
|
|
if err := db.Save(&address).Error; err != nil {
|
|
utils.RespondError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to save address", nil)
|
|
return
|
|
}
|
|
|
|
utils.RespondSuccess(w, http.StatusOK, map[string]any{
|
|
"address": address,
|
|
})
|
|
}
|