saved mock listing

This commit is contained in:
2026-02-04 22:48:57 -07:00
parent a8dda86882
commit 813fbb11b1
13 changed files with 792 additions and 369 deletions
-275
View File
@@ -10,7 +10,6 @@ import (
"ruben/inventory2/internal/consts"
"ruben/inventory2/internal/logging"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -43,28 +42,6 @@ type (
Description string
Count int64
}
// ids
AccountIDs struct {
AccountID int64
}
AccountShopIDs struct {
AccountIDs
Platform Platform
ShopID string
}
AccountShopListingIDs struct {
AccountShopIDs
ListingID string
}
MockShop struct {
AccountShopIDs
Name string
}
)
func NewStore(logger *logging.Logger, db *pgxpool.Pool) *Store {
@@ -315,258 +292,6 @@ func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform
return vs, nil
}
func (db *Store) CreateMockShop(ctx context.Context, acctID int64, platform Platform, name string) (shopID uuid.UUID, err error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return uuid.Nil, err
}
tx, err := db.db.Begin(ctx)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to being transaction: %w", err)
}
defer tx.Rollback(ctx)
// TODO: are we really going to just copy the id between accounts and mock.accounts?
// upsert mock account
rows, err := tx.Query(
ctx,
`
WITH existing_account(account_id, user_id) AS (
SELECT
account_id,
user_id
FROM
accounts
WHERE
account_id = @account_id
), existing_mock_account AS (
SELECT
user_id
FROM
accounts
WHERE
account_id = @account_id
), inserted_mock_account AS (
INSERT INTO
mock.accounts (
account_id,
user_id
)
SELECT
account_id,
user_id
FROM
existing_account
ON CONFLICT
DO NOTHING
RETURNING
user_id
)
SELECT
COALESCE(ia.user_id, ea.user_id)
FROM
inserted_mock_account ia
FULL OUTER JOIN
existing_mock_account ea
USING
(user_id)
`,
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("test query failed: %w", err)
}
userID, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return uuid.Nil, fmt.Errorf("%w: account not found", consts.ErrNotFound)
}
return uuid.Nil, fmt.Errorf("failed to scan rows: %w", err)
}
// create the mock shop
shopID = uuid.New()
_, err = tx.Exec(
ctx,
fmt.Sprintf(
`
INSERT INTO
%s (
user_id,
account_id,
shop_id,
name
)
VALUES (
@user_id,
@account_id,
@shop_id,
@name
)
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"user_id": userID,
"shop_id": shopID,
"name": name,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to perform query to create mock shop: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return uuid.Nil, fmt.Errorf("failed to commit transaction: %w", err)
}
return shopID, nil
}
func (db *Store) ListMockShopsForPlatform(ctx context.Context, acctID int64, platform Platform) ([]MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
shop_id,
name
FROM
%s
WHERE
account_id = @account_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Shop_id uuid.UUID
Name string
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
shops := make([]MockShop, len(vs))
for i, v := range vs {
shops[i] = MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: v.Shop_id.String(),
},
Name: v.Name,
}
}
return shops, nil
}
func (db *Store) GetMockShop(ctx context.Context, acctID int64, platform Platform, shopID string) (*MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
name
FROM
%s
WHERE
account_id = @account_id
AND shop_id = @shop_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"shop_id": shopID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
name, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, consts.ErrNotFound
}
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
return &MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: shopID,
},
Name: name,
}, nil
}
func getMockShopTableName(platform Platform) (string, error) {
switch platform {
case Amazon:
return "mock.shop_amazon", nil
case BigCartel:
return "mock.shop_big_cartel", nil
case Ebay:
return "mock.shop_ebay", nil
case Ecwid:
return "mock.shop_ecwid", nil
case Etsy:
return "mock.shop_etsy", nil
case Shopify:
return "mock.shop_shopify", nil
case SquareOnline:
return "mock.shop_square_online", nil
case Squarespace:
return "mock.shop_squarespace", nil
case Tiktok:
return "mock.shop_tiktok", nil
case WalmartMarketplace:
return "mock.shop_walmart_marketplace", nil
case Wix:
return "mock.shop_wix", nil
case WooCommerce:
return "mock.shop_woo_commerce", nil
case Zoho:
return "mock.shop_zoho", nil
default:
return "", fmt.Errorf("%w: unrecognized platform", consts.ErrBadRequest)
}
}
// additional context
func (db *Store) GetAccountPointerByUserID(ctx context.Context, userID string) (*Account, error) {
+39
View File
@@ -0,0 +1,39 @@
package accounts
type (
AccountIDs struct {
AccountID int64
}
AccountShopIDs struct {
AccountIDs
Platform Platform
ShopID string
}
AccountShopListingIDs struct {
AccountShopIDs
ListingID string
}
)
func NewAccountIDs(acctID int64) AccountIDs {
return AccountIDs{
AccountID: acctID,
}
}
func (ids AccountIDs) ShopID(p Platform, id string) AccountShopIDs {
return AccountShopIDs{
AccountIDs: ids,
Platform: p,
ShopID: id,
}
}
func (ids AccountShopIDs) ListingID(id string) AccountShopListingIDs {
return AccountShopListingIDs{
AccountShopIDs: ids,
ListingID: id,
}
}
+457
View File
@@ -0,0 +1,457 @@
package accounts
import (
"context"
"errors"
"fmt"
"ruben/inventory2/internal/consts"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
type (
MockShop struct {
AccountShopIDs
Name string
}
MockListing struct {
AccountShopListingIDs
SKU string
Name string
Description string
Count int64
}
)
func (db *Store) CreateMockShop(ctx context.Context, acctID int64, platform Platform, name string) (shopID uuid.UUID, err error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return uuid.Nil, err
}
tx, err := db.db.Begin(ctx)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to being transaction: %w", err)
}
defer tx.Rollback(ctx)
// TODO: are we really going to just copy the id between accounts and mock.accounts?
// upsert mock account
rows, err := tx.Query(
ctx,
`
WITH existing_account(account_id, user_id) AS (
SELECT
account_id,
user_id
FROM
accounts
WHERE
account_id = @account_id
), existing_mock_account AS (
SELECT
user_id
FROM
accounts
WHERE
account_id = @account_id
), inserted_mock_account AS (
INSERT INTO
mock.accounts (
account_id,
user_id
)
SELECT
account_id,
user_id
FROM
existing_account
ON CONFLICT
DO NOTHING
RETURNING
user_id
)
SELECT
COALESCE(ia.user_id, ea.user_id)
FROM
inserted_mock_account ia
FULL OUTER JOIN
existing_mock_account ea
USING
(user_id)
`,
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to perform query: %w", err)
}
userID, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return uuid.Nil, fmt.Errorf("%w: account not found", consts.ErrNotFound)
}
return uuid.Nil, fmt.Errorf("failed to scan rows: %w", err)
}
// create the mock shop
shopID = uuid.New()
_, err = tx.Exec(
ctx,
fmt.Sprintf(
`
INSERT INTO
%s (
user_id,
account_id,
shop_id,
name
)
VALUES (
@user_id,
@account_id,
@shop_id,
@name
)
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"user_id": userID,
"shop_id": shopID,
"name": name,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to perform query to create mock shop: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return uuid.Nil, fmt.Errorf("failed to commit transaction: %w", err)
}
return shopID, nil
}
func (db *Store) ListMockShopsForPlatform(ctx context.Context, acctID int64, platform Platform) ([]MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
shop_id,
name
FROM
%s
WHERE
account_id = @account_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Shop_id uuid.UUID
Name string
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
shops := make([]MockShop, len(vs))
for i, v := range vs {
shops[i] = MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: v.Shop_id.String(),
},
Name: v.Name,
}
}
return shops, nil
}
func (db *Store) GetMockShop(ctx context.Context, acctID int64, platform Platform, shopID string) (*MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
name
FROM
%s
WHERE
account_id = @account_id
AND shop_id = @shop_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"shop_id": shopID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
name, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, consts.ErrNotFound
}
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
return &MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: shopID,
},
Name: name,
}, nil
}
func (db *Store) CreateMockListing(ctx context.Context, listing MockListing) (MockListing, error) {
shopTableName, listingTableName, err := getMockShopAndListingsTableNames(listing.Platform)
if err != nil {
return MockListing{}, err
}
listing.ListingID = uuid.New().String()
tag, err := db.db.Exec(
ctx,
fmt.Sprintf(
`
WITH existing_shop(account_id, shop_id) AS (
SELECT
account_id,
shop_id
FROM
%s
WHERE
account_id = @account_id
AND shop_id = @shop_id
), new_values(listing_id, sku, name, description, "count") AS (
SELECT
@listing_id::text,
@sku::text,
@name::text,
@description::text,
@count::integer
)
INSERT INTO
%s (
account_id,
shop_id,
listing_id,
sku,
name,
description,
count
)
SELECT
account_id,
shop_id,
listing_id,
sku,
name,
description,
"count"
FROM
existing_shop
JOIN
new_values
ON
true
`,
shopTableName,
listingTableName,
),
pgx.NamedArgs{
"account_id": listing.AccountID,
"shop_id": listing.ShopID,
"listing_id": listing.ListingID,
"sku": listing.SKU,
"name": listing.Name,
"description": listing.Description,
"count": listing.Count + 5,
},
)
if err != nil {
return MockListing{}, fmt.Errorf("failed to perform query: %w", err)
}
if tag.RowsAffected() == 0 {
return MockListing{}, fmt.Errorf("%w: shop not found", consts.ErrNotFound)
}
return listing, nil
}
func (db *Store) ListMockListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]MockListing, error) {
shopTableName, listingsTableName, err := getMockShopAndListingsTableNames(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
WITH shop_exists(shop_exists) AS (
SELECT
true
FROM
%s
WHERE
account_id = @account_id
AND shop_id = @shop_id
)
SELECT
COALESCE(shop_exists, false) AS shop_exists,
listing_id,
sku,
name,
description,
"count"
FROM
shop_exists
LEFT JOIN
%s
ON
TRUE
WHERE
account_id = @account_id
AND shop_id = @shop_id
`,
shopTableName,
listingsTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"shop_id": shopID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Shop_exists bool
Listing_id string
SKU string
Name string
Description string
Count int64
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
// check if the shop exists, and confirm there were actually listings (LEFT JOIN)
if len(vs) == 0 || !vs[0].Shop_exists {
return nil, fmt.Errorf("%w: shop not found", consts.ErrNotFound)
}
if vs[0].SKU == "" {
return nil, nil
}
listings := make([]MockListing, len(vs))
for i, v := range vs {
listings[i] = MockListing{
AccountShopListingIDs: AccountShopListingIDs{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: shopID,
},
ListingID: v.Listing_id,
},
SKU: v.SKU,
Name: v.Name,
Description: v.Description,
Count: v.Count,
}
}
return listings, nil
}
func getMockShopTableName(platform Platform) (string, error) {
switch platform {
case Amazon:
return "mock.shop_amazon", nil
case BigCartel:
return "mock.shop_big_cartel", nil
case Ebay:
return "mock.shop_ebay", nil
case Ecwid:
return "mock.shop_ecwid", nil
case Etsy:
return "mock.shop_etsy", nil
case Shopify:
return "mock.shop_shopify", nil
case SquareOnline:
return "mock.shop_square_online", nil
case Squarespace:
return "mock.shop_squarespace", nil
case Tiktok:
return "mock.shop_tiktok", nil
case WalmartMarketplace:
return "mock.shop_walmart_marketplace", nil
case Wix:
return "mock.shop_wix", nil
case WooCommerce:
return "mock.shop_woo_commerce", nil
case Zoho:
return "mock.shop_zoho", nil
default:
return "", fmt.Errorf("%w: unrecognized platform", consts.ErrBadRequest)
}
}
func getMockShopAndListingsTableNames(platform Platform) (shop string, listing string, err error) {
shop, err = getMockShopTableName(platform)
if err != nil {
return "", "", err
}
return shop, shop + "_listings", nil
}
@@ -48,6 +48,10 @@ func (v_ctx *StoreWithContext) GetListingsForShop(acctID int64, platform Platfor
return v_ctx.Store.GetListingsForShop(v_ctx.ctx, acctID, platform, shopID)
}
func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Account, error) {
return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID)
}
func (v_ctx *StoreWithContext) CreateMockShop(acctID int64, platform Platform, name string) (uuid.UUID, error) {
return v_ctx.Store.CreateMockShop(v_ctx.ctx, acctID, platform, name)
}
@@ -60,8 +64,12 @@ func (v_ctx *StoreWithContext) GetMockShop(acctID int64, platform Platform, shop
return v_ctx.Store.GetMockShop(v_ctx.ctx, acctID, platform, shopID)
}
func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Account, error) {
return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID)
func (v_ctx *StoreWithContext) CreateMockListing(listing MockListing) (MockListing, error) {
return v_ctx.Store.CreateMockListing(v_ctx.ctx, listing)
}
func (v_ctx *StoreWithContext) ListMockListingsForShop(acctID int64, platform Platform, shopID string) ([]MockListing, error) {
return v_ctx.Store.ListMockListingsForShop(v_ctx.ctx, acctID, platform, shopID)
}
func (v_ctx *StoreWithContext) SetOrderOfPlatformOnAccountPage(acctID int64, platform Platform, orderIndex int) ([]Platform, int, error) {
+16 -5
View File
@@ -269,6 +269,8 @@ func (s *accountSubrouter) createMockShop(c *gin.Context) (response.Response, er
// 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
@@ -287,11 +289,20 @@ func (s *accountSubrouter) addMockListing(c *gin.Context) (response.Response, er
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)
_, err = s.accts.CreateMockListing(
c,
accounts.MockListing{
AccountShopListingIDs: accounts.NewAccountIDs(acctID).
ShopID(platform, shopID).
ListingID(""),
Name: name,
SKU: sku,
Description: description,
},
)
if err != nil {
return nil, fmt.Errorf("failed to create new listing: %w", err)
}
return nil, nil
}