package accounts import ( "context" "errors" "fmt" "ruben/inventory2/internal/consts" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) 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, }, ) 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 } // UpdateMockListing updates a mock listing, but not it's count func (db *Store) UpdateMockListing(ctx context.Context, listing MockListing) error { _, listingTableName, err := getMockShopAndListingsTableNames(listing.Platform) if err != nil { return err } tag, err := db.db.Exec( ctx, fmt.Sprintf( ` UPDATE %s SET sku = @sku, name = @name, description = @description WHERE account_id = @account_id AND shop_id = @shop_id AND listing_id = @listing_id `, listingTableName, ), pgx.NamedArgs{ "account_id": listing.AccountID, "shop_id": listing.ShopID, "listing_id": listing.ListingID, "sku": listing.SKU, "name": listing.Name, "description": listing.Description, }, ) if err != nil { return fmt.Errorf("failed to perform query: %w", err) } if tag.RowsAffected() == 0 { return fmt.Errorf("%w: listing not found", consts.ErrNotFound) } return 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 IS NULL OR account_id = @account_id) AND (shop_id IS NULL OR 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 pgtype.Text SKU pgtype.Text Name pgtype.Text Description pgtype.Text Count pgtype.Int8 }]) 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 { fmt.Println("TEST") return nil, fmt.Errorf("%w: shop not found", consts.ErrNotFound) } if !vs[0].SKU.Valid { 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.String, }, SKU: v.SKU.String, Name: v.Name.String, Description: v.Description.String, Count: v.Count.Int64, } } 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 }