stubbed mock shop creation route

This commit is contained in:
2026-02-03 08:28:07 -07:00
parent e74854bf6e
commit a8dda86882
8 changed files with 381 additions and 153 deletions
+77 -82
View File
@@ -3,7 +3,6 @@ package accounts
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/gin-gonic/gin"
@@ -13,6 +12,7 @@ import (
"ruben/inventory2/internal/domains/accounts"
"ruben/inventory2/internal/logging"
"ruben/inventory2/internal/server/auth"
"ruben/inventory2/internal/server/param"
"ruben/inventory2/internal/server/response"
"ruben/inventory2/internal/server/sse"
)
@@ -41,6 +41,9 @@ func Routes(
platformGroup.PUT("/order-index", response.Handler(as.setOrderOfPlatformOnAccountPage))
platformGroup.POST("/shops/mocks", response.Handler(as.createMockShop))
mockShops := platformGroup.Group("/shops/mocks/:shop-id")
mockShops.POST("/listings", response.Handler(as.addMockListing))
syncGroups := r.Group("/:acctID/inventory/sync-groups")
syncGroups.POST("", pub.Publish("/:acctID/inventory/sync-groups"), response.Handler(as.saveNewSyncGroup))
@@ -89,7 +92,7 @@ func (s *accountSubrouter) createSyncGroupListingDraft(c *gin.Context) (response
return response.StatusCreated(), nil
}
// PUT /:acctID/inventory/sync-groups/draft/listings/{orderIndex}/shop
// PUT /:acctID/inventory/sync-groups/draft/listings/:orderIndex/shop
// @platform string
// @shopID string
func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
@@ -97,24 +100,19 @@ func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (respo
ctx := r.Context()
acctID := auth.GetIdentity(ctx).Account.AccountID
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
var (
orderIndex int
platform accounts.Platform
shopID string
)
err := param.Path("orderIndex", param.Int(&orderIndex)).
Form("platform", param.Platform(&platform)).
Unmarshal(c)
if err != nil {
return nil, err
}
platformStr := r.FormValue("platform")
platform, err := accounts.NewPlatform(platformStr)
if err != nil {
return nil, response.BadRequest().
Msgf("unrecognized platform: %s", platformStr)
}
shopID := r.FormValue("shop-id")
if shopID == "" {
return nil, response.BadRequest().
Msg("no shop-id provided")
}
if err := s.accts.SetShopInSyncGroupListingDraft(ctx, acctID, orderIndex, platform, shopID); err != nil {
return nil, response.Errorf("failed to set shop: %w", response.ErrorFromConstant(err))
}
@@ -122,23 +120,24 @@ func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (respo
return response.StatusOK(), nil
}
// PUT /:acctID/inventory/sync-groups/draft/listings/{orderIndex}/listing
// PUT /:acctID/inventory/sync-groups/draft/listings/:orderIndex/listing
func (s *accountSubrouter) setListingInSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
r := c.Request
ctx := r.Context()
acctID := auth.GetIdentity(ctx).Account.AccountID
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
var (
orderIndex int
listingID string
)
err := param.Path("orderIndex", param.Int(&orderIndex)).
Form("listing-id", param.Text(&listingID)).
Unmarshal(c)
if err != nil {
return nil, err
}
listingID := r.FormValue("listing-id")
if listingID == "" {
return nil, response.BadRequest().
Msg("no listing-id provided")
}
if err := s.accts.SetListingInSyncGroupListingDraft(ctx, acctID, orderIndex, listingID); err != nil {
return nil, response.Errorf("failed to set listing: %w", response.ErrorFromConstant(err))
}
@@ -146,13 +145,18 @@ func (s *accountSubrouter) setListingInSyncGroupListingDraft(c *gin.Context) (re
return response.StatusOK(), nil
}
// DELETE /:acctID/inventory/sync-groups/draft/listings/{orderIndex}
// DELETE /:acctID/inventory/sync-groups/draft/listings/:orderIndex
func (s *accountSubrouter) deleteSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
r := c.Request
ctx := r.Context()
acctID := auth.GetIdentity(ctx).Account.AccountID
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
var (
orderIndex int
)
err := param.Path("orderIndex", param.Int(&orderIndex)).
Unmarshal(c)
if err != nil {
return nil, err
}
@@ -177,22 +181,6 @@ func (s *accountSubrouter) saveNewSyncGroup(c *gin.Context) (response.Response,
return response.StatusCreated(), nil
}
func getOrderIndexForSyncGroupListingDraftFromPath(c *gin.Context) (int, error) {
orderIndexStr := c.Param("orderIndex")
if orderIndexStr == "" {
return 0, response.NotFound().
Msg("no orderIndex found")
}
orderIndex, err := strconv.Atoi(orderIndexStr)
if err != nil {
return 0, response.NotFound().
Msgf("invalid order index: %s", orderIndexStr)
}
return orderIndex, nil
}
// PUT /:acctID/platforms/:platform/order-index"
// this endpoint is called when dragging a platform tab in the accounts page.
func (s *accountSubrouter) setOrderOfPlatformOnAccountPage(c *gin.Context) (response.Response, error) {
@@ -200,30 +188,16 @@ func (s *accountSubrouter) setOrderOfPlatformOnAccountPage(c *gin.Context) (resp
// validate parameters
var orderIndex int
if v, ok := c.GetPostForm("order-index"); !ok {
return nil, response.BadRequest().
Msg("no order-index provided")
} else if i, err := strconv.Atoi(v); err != nil {
return nil, response.BadRequest().
Msgf("order-index must be a non-negative integer: %s", v)
} else if i < 0 {
return nil, response.BadRequest().
Msgf("order-index must be a non-negative integer: %s", v)
} else {
orderIndex = i
}
var (
platform accounts.Platform
orderIndex int
)
var platform accounts.Platform
if v := c.Param("platform"); v == "" {
return nil, response.BadRequest().
Msg("no platform provided")
} else if p, err := accounts.NewPlatform(v); err != nil {
return nil, response.BadRequest().
Wrap(err).
Msgf("unrecognized platform: %v", v)
} else {
platform = p
err := param.Path("platform", param.Platform(&platform)).
Form("orderIndex", param.Int(&orderIndex)).
Unmarshal(c)
if err != nil {
return nil, err
}
// update the order
@@ -260,24 +234,16 @@ func (s *accountSubrouter) createMockShop(c *gin.Context) (response.Response, er
// validate parameters
var platform accounts.Platform
if v := c.Param("platform"); v == "" {
return nil, response.BadRequest().
Msg("no platform provided")
} else if p, err := accounts.NewPlatform(v); err != nil {
return nil, response.BadRequest().
Wrap(err).
Msgf("unrecognized platform: %v", v)
} else {
platform = p
}
var (
platform accounts.Platform
name string
)
var name string
if v, ok := c.GetPostForm("name"); !ok || v == "" {
return nil, response.BadRequest().
Msg("no order-index provided")
} else {
name = v
err := param.Path("platform", param.Platform(&platform)).
Form("name", param.Text(&name)).
Unmarshal(c)
if err != nil {
return nil, err
}
s.log.Warnf("not implemented: mock store created: account_id = %d; platform = %s; name = %s", acctID, platform, name)
@@ -301,6 +267,35 @@ func (s *accountSubrouter) createMockShop(c *gin.Context) (response.Response, er
return res.HXLocation(location), nil
}
// POST /:acctID/platforms/:platform/shops/mocks/:shop-id/listings
func (s *accountSubrouter) addMockListing(c *gin.Context) (response.Response, error) {
var (
platform accounts.Platform
shopID string
name string
sku string
description string
)
err := param.Path("platform", param.Platform(&platform)).
Path("shop-id", param.Text(&shopID)).
Form("name", param.Text(&name)).
Form("sku", param.Text(&sku)).
Form("description", param.Text(&description)).
Unmarshal(c)
if err != nil {
return nil, err
}
s.log.Debugf("platform: %v", platform)
s.log.Debugf("shopID: %v", shopID)
s.log.Debugf("name: %v", name)
s.log.Debugf("sku: %v", sku)
s.log.Debugf("description: %v", description)
return nil, nil
}
func lowerSnakeCase(s accounts.Platform) string {
return strings.ToLower(strings.Join(strings.Split(string(s), " "), "_"))
}
+50
View File
@@ -0,0 +1,50 @@
package param
import (
"encoding"
"ruben/inventory2/internal/domains/accounts"
"strconv"
)
// encoding.TextUnmarshaler instances for Spec
func Text(dst *string) encoding.TextUnmarshaler {
return (*rawText)(dst)
}
func Int(dst *int) encoding.TextUnmarshaler {
return (*intText)(dst)
}
func Platform(dst *accounts.Platform) encoding.TextUnmarshaler {
return (*platformText)(dst)
}
type (
rawText string
intText int
platformText accounts.Platform
)
func (t *rawText) UnmarshalText(text []byte) error {
*t = rawText(text)
return nil
}
func (n *intText) UnmarshalText(text []byte) error {
i, err := strconv.Atoi(string(text))
if err != nil {
return err
}
*n = intText(i)
return nil
}
func (p *platformText) UnmarshalText(text []byte) error {
v, err := accounts.NewPlatform(string(text))
if err != nil {
return err
}
*p = platformText(v)
return nil
}
+76
View File
@@ -0,0 +1,76 @@
package param
import (
"encoding"
"github.com/gin-gonic/gin"
"ruben/inventory2/internal/server/response"
)
// Spec is the entry point of the package.
// It's typically constructed via Path() or Form().
// But it's zero value is valid.
//
// It's methods support a builder pattern to minimize API bloat.
//
// To complete gin parameter parsing, call Unmarshal().
type Spec struct {
path map[string]encoding.TextUnmarshaler
form map[string]encoding.TextUnmarshaler
}
func Path(k string, dst encoding.TextUnmarshaler) Spec {
return Spec{
path: map[string]encoding.TextUnmarshaler{
k: dst,
},
}
}
func Form(k string, dst encoding.TextUnmarshaler) Spec {
return Spec{
form: map[string]encoding.TextUnmarshaler{
k: dst,
},
}
}
func (s Spec) Path(k string, dst encoding.TextUnmarshaler) Spec {
if s.path == nil {
s.path = make(map[string]encoding.TextUnmarshaler, 1)
}
s.path[k] = dst
return s
}
func (s Spec) Form(k string, dst encoding.TextUnmarshaler) Spec {
if s.form == nil {
s.form = make(map[string]encoding.TextUnmarshaler, 1)
}
s.form[k] = dst
return s
}
func (s Spec) Unmarshal(c *gin.Context) error {
for k, dst := range s.path {
v := c.Param(k)
if v == "" {
return response.NotFound().Msgf("no %s provided", k)
}
if err := dst.UnmarshalText([]byte(v)); err != nil {
return response.NotFound().Wrap(err).Msgf("invalid %s", k)
}
}
for k, dst := range s.form {
v, ok := c.GetPostForm(k)
if !ok || v == "" {
return response.BadRequest().Msgf("no %s provided", k)
}
if err := dst.UnmarshalText([]byte(v)); err != nil {
return response.BadRequest().Wrap(err).Msgf("invalid %s provided", k)
}
}
return nil
}
+13
View File
@@ -100,6 +100,19 @@ func Routes(
"rawHTML": func(s string) template.HTML {
return template.HTML(s)
},
"rawHTMLAttr": func(s string) template.HTMLAttr {
return template.HTMLAttr(s)
},
"style": func(kvs ...string) (template.HTMLAttr, error) {
if len(kvs)%2 != 0 {
return "", fmt.Errorf("expected an even number of keys: %d", len(kvs))
}
parts := make([]string, len(kvs)/2)
for i := range parts {
parts[i] = fmt.Sprintf("%s: %s;", kvs[2*i], kvs[2*i+1])
}
return template.HTMLAttr(strings.Join(parts, " ")), nil
},
// json
"prettyPrintJSON": func(j json.RawMessage) string {