394 lines
11 KiB
Go
394 lines
11 KiB
Go
package accounts
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"ruben/inventory2/internal/consts"
|
|
"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"
|
|
)
|
|
|
|
type accountSubrouter struct {
|
|
log *logging.Logger
|
|
accts *accounts.Store
|
|
pub *sse.UpdateNotificationPublisher
|
|
}
|
|
|
|
func Routes(
|
|
r *gin.RouterGroup,
|
|
logger *logging.Logger,
|
|
accts *accounts.Store,
|
|
pub *sse.UpdateNotificationPublisher,
|
|
) {
|
|
as := &accountSubrouter{
|
|
log: logger,
|
|
accts: accts,
|
|
pub: pub,
|
|
}
|
|
|
|
r.POST("", response.Handler(as.createAccount))
|
|
|
|
platformGroup := r.Group("/:acctID/platforms/:platform", pub.Publish("/:acctID/platforms"))
|
|
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))
|
|
mockShops.PUT("/listings/:listing-id", response.Handler(as.updateMockListing))
|
|
mockShops.DELETE("/listings/:listing-id", response.Handler(as.deleteMockListing))
|
|
|
|
syncGroups := r.Group("/:acctID/inventory/sync-groups")
|
|
syncGroups.POST("", pub.Publish("/:acctID/inventory/sync-groups"), response.Handler(as.saveNewSyncGroup))
|
|
|
|
draftListings := syncGroups.Group("/draft/listings", pub.Publish("/:acctID/inventory/sync-groups/draft/listings"))
|
|
draftListings.POST("", response.Handler(as.createSyncGroupListingDraft))
|
|
draftListings.PUT("/:orderIndex/shop", response.Handler(as.setShopInSyncGroupListingDraft))
|
|
draftListings.PUT("/:orderIndex/listing", response.Handler(as.setListingInSyncGroupListingDraft))
|
|
draftListings.DELETE("/:orderIndex", response.Handler(as.deleteSyncGroupListingDraft))
|
|
}
|
|
|
|
// POST /
|
|
func (s *accountSubrouter) createAccount(c *gin.Context) (response.Response, error) {
|
|
r := c.Request
|
|
ctx := r.Context()
|
|
|
|
// TODO: get email from the user's account profile (how?) - will need to flesh out the account creation process later on
|
|
email := uuid.NewString() + "@example.com"
|
|
|
|
userID := auth.GetIdentity(ctx).User.UserID
|
|
|
|
_, err := s.accts.CreateAccount(ctx, userID, email)
|
|
if err != nil {
|
|
if errors.Is(err, consts.ErrConflict) {
|
|
return nil, response.Conflict().
|
|
Msg("user already has an account")
|
|
}
|
|
return nil, response.Errorf("failed to create account: %w", err)
|
|
}
|
|
|
|
return response.StatusCreated().
|
|
// force a reload to ensure an sse connection is made.
|
|
// user should automatically be redirected to the appropriate page based on their current location and/or login/account statuses.
|
|
HXRefresh("true"), nil
|
|
}
|
|
|
|
// POST /:acctID/inventory/sync-groups/draft/listings
|
|
func (s *accountSubrouter) createSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
|
r := c.Request
|
|
ctx := r.Context()
|
|
acctID := auth.GetIdentity(ctx).Account.AccountID
|
|
|
|
if _, err := s.accts.CreateSyncGroupListingDraft(ctx, acctID); err != nil {
|
|
return nil, response.Errorf("failed to create new listing draft: %w", err)
|
|
}
|
|
|
|
return response.StatusCreated(), nil
|
|
}
|
|
|
|
// PUT /:acctID/inventory/sync-groups/draft/listings/:orderIndex/shop
|
|
// @platform string
|
|
// @shopID string
|
|
func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
|
r := c.Request
|
|
ctx := r.Context()
|
|
acctID := auth.GetIdentity(ctx).Account.AccountID
|
|
|
|
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
|
|
}
|
|
|
|
if err := s.accts.SetShopInSyncGroupListingDraft(ctx, acctID, orderIndex, platform, shopID); err != nil {
|
|
return nil, response.Errorf("failed to set shop: %w", response.ErrorFromConstant(err))
|
|
}
|
|
|
|
return response.StatusOK(), nil
|
|
}
|
|
|
|
// 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
|
|
|
|
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
|
|
}
|
|
|
|
if err := s.accts.SetListingInSyncGroupListingDraft(ctx, acctID, orderIndex, listingID); err != nil {
|
|
return nil, response.Errorf("failed to set listing: %w", response.ErrorFromConstant(err))
|
|
}
|
|
|
|
return response.StatusOK(), nil
|
|
}
|
|
|
|
// 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
|
|
|
|
var (
|
|
orderIndex int
|
|
)
|
|
|
|
err := param.Path("orderIndex", param.Int(&orderIndex)).
|
|
Unmarshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := s.accts.DeleteSyncGroupListingDraft(ctx, acctID, orderIndex); err != nil {
|
|
return nil, response.Errorf("failed to delete listing: %w", response.ErrorFromConstant(err))
|
|
}
|
|
|
|
return response.StatusOK(), nil
|
|
}
|
|
|
|
// POST /:acctID/inventory/sync-groups
|
|
func (s *accountSubrouter) saveNewSyncGroup(c *gin.Context) (response.Response, error) {
|
|
r := c.Request
|
|
ctx := r.Context()
|
|
acctID := auth.GetIdentity(ctx).Account.AccountID
|
|
|
|
if _, err := s.accts.SaveNewSyncGroup(ctx, acctID); err != nil {
|
|
return nil, response.Errorf("failed to save new sync group: %w", response.ErrorFromConstant(err))
|
|
}
|
|
|
|
return response.StatusCreated(), 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) {
|
|
acctID := auth.GetIdentity(c).Account.AccountID
|
|
|
|
// validate parameters
|
|
|
|
var (
|
|
platform accounts.Platform
|
|
orderIndex int
|
|
)
|
|
|
|
err := param.Path("platform", param.Platform(&platform)).
|
|
Form("orderIndex", param.Int(&orderIndex)).
|
|
Unmarshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// update the order
|
|
|
|
platforms, prevIndex, err := s.accts.SetOrderOfPlatformOnAccountPage(c, acctID, platform, orderIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save record: %w", err)
|
|
}
|
|
|
|
// emit events on all platforms updated, so the tabs can all refresh (including their logic)
|
|
|
|
platformEvents := make([]string, max(orderIndex, prevIndex)-min(orderIndex, prevIndex))
|
|
if orderIndex > prevIndex {
|
|
for i := range orderIndex - prevIndex {
|
|
p := platforms[prevIndex+i]
|
|
platformEvents[i] = fmt.Sprintf("accounts_%d_platforms_%s_order-index", acctID, lowerSnakeCase(p))
|
|
}
|
|
} else if orderIndex < prevIndex {
|
|
for i := range prevIndex - orderIndex {
|
|
p := platforms[orderIndex+1+i]
|
|
platformEvents[i] = fmt.Sprintf("accounts_%d_platforms_%s_order-index", acctID, lowerSnakeCase(p))
|
|
}
|
|
}
|
|
if err := s.pub.Push(c, acctID, platformEvents...); err != nil {
|
|
s.log.Errorf("failed to publish platform order-index events: %v", err)
|
|
}
|
|
|
|
return response.StatusNoContent(), nil
|
|
}
|
|
|
|
// POST /:acctID/platforms/:platform/shops/mocks
|
|
func (s *accountSubrouter) createMockShop(c *gin.Context) (response.Response, error) {
|
|
acctID := auth.GetIdentity(c).Account.AccountID
|
|
|
|
// validate parameters
|
|
|
|
var (
|
|
platform accounts.Platform
|
|
name string
|
|
)
|
|
|
|
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)
|
|
|
|
// create the mock account
|
|
|
|
id, err := s.accts.CreateMockShop(c, acctID, platform, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save record: %w", err)
|
|
}
|
|
|
|
location := fmt.Sprintf("/ui/accounts/%d/platforms/%s/mock-shops/%s", acctID, lowerSnakeCase(platform), id)
|
|
|
|
var res response.Response
|
|
if c.GetHeader("HX-Request") == "true" {
|
|
res = response.StatusCreated()
|
|
} else {
|
|
res = response.SeeOther(location)
|
|
}
|
|
|
|
return res.HXLocation(location), nil
|
|
}
|
|
|
|
// POST /:acctID/platforms/:platform/shops/mocks/:shop-id/listings
|
|
func (s *accountSubrouter) addMockListing(c *gin.Context) (response.Response, error) {
|
|
acctID := auth.GetIdentity(c).Account.AccountID
|
|
|
|
var (
|
|
platform accounts.Platform
|
|
shopID string
|
|
name string
|
|
sku string
|
|
description string
|
|
count int
|
|
)
|
|
|
|
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)).
|
|
Form("count", param.Int(&count)).
|
|
Unmarshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = s.accts.CreateMockListing(
|
|
c,
|
|
accounts.MockListing{
|
|
AccountShopListingIDs: accounts.NewAccountIDs(acctID).
|
|
ShopID(platform, shopID).
|
|
ListingID(""),
|
|
Name: name,
|
|
SKU: sku,
|
|
Description: description,
|
|
Count: int64(count),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create new listing: %w", err)
|
|
}
|
|
|
|
return response.StatusCreated(), nil
|
|
}
|
|
|
|
// PUT /:acctID/platforms/:platform/shops/mocks/:shop-id/listings/:listing-id
|
|
func (s *accountSubrouter) updateMockListing(c *gin.Context) (response.Response, error) {
|
|
acctID := auth.GetIdentity(c).Account.AccountID
|
|
|
|
var (
|
|
platform accounts.Platform
|
|
shopID string
|
|
listingID string
|
|
name string
|
|
sku string
|
|
description string
|
|
count int
|
|
)
|
|
|
|
err := param.Path("platform", param.Platform(&platform)).
|
|
Path("shop-id", param.Text(&shopID)).
|
|
Path("listing-id", param.Text(&listingID)).
|
|
Form("name", param.Text(&name)).
|
|
Form("sku", param.Text(&sku)).
|
|
Form("description", param.Text(&description)).
|
|
Form("count", param.Int(&count)).
|
|
Unmarshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = s.accts.UpdateMockListing(
|
|
c,
|
|
accounts.MockListing{
|
|
AccountShopListingIDs: accounts.NewAccountIDs(acctID).
|
|
ShopID(platform, shopID).
|
|
ListingID(listingID),
|
|
Name: name,
|
|
SKU: sku,
|
|
Description: description,
|
|
Count: int64(count),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update listing: %w", err)
|
|
}
|
|
|
|
return response.StatusOK(), nil
|
|
}
|
|
|
|
// DELETE /:acctID/platforms/:platform/shops/mocks/:shop-id/listings/:listing-id
|
|
func (s *accountSubrouter) deleteMockListing(c *gin.Context) (response.Response, error) {
|
|
acctID := auth.GetIdentity(c).Account.AccountID
|
|
|
|
var (
|
|
platform accounts.Platform
|
|
shopID string
|
|
listingID string
|
|
)
|
|
|
|
err := param.Path("platform", param.Platform(&platform)).
|
|
Path("shop-id", param.Text(&shopID)).
|
|
Path("listing-id", param.Text(&listingID)).
|
|
Unmarshal(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = s.accts.DeleteMockListing(
|
|
c,
|
|
accounts.NewAccountIDs(acctID).
|
|
ShopID(platform, shopID).
|
|
ListingID(listingID),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to delete listing: %w", err)
|
|
}
|
|
|
|
return response.StatusNoContent(), nil
|
|
}
|
|
|
|
func lowerSnakeCase(s accounts.Platform) string {
|
|
return strings.ToLower(strings.Join(strings.Split(string(s), " "), "_"))
|
|
}
|