removed intermediate /internal directory
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
package accounts
|
||||
|
||||
// to generate StoreWithContext
|
||||
//go:generate concurry -s Store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"ruben/inventory2/consts"
|
||||
"ruben/inventory2/logging"
|
||||
)
|
||||
|
||||
type (
|
||||
Store struct {
|
||||
log *logging.Logger
|
||||
db *pgxpool.Pool
|
||||
}
|
||||
|
||||
Account struct {
|
||||
AccountIDs
|
||||
OAuthUser
|
||||
Email string
|
||||
}
|
||||
|
||||
OAuthUser struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
AccountShop struct {
|
||||
AccountShopIDs
|
||||
Name string
|
||||
}
|
||||
|
||||
Listing struct {
|
||||
AccountShopListingIDs
|
||||
SKU string
|
||||
Name string
|
||||
Description string
|
||||
Count int64
|
||||
}
|
||||
)
|
||||
|
||||
func NewStore(logger *logging.Logger, db *pgxpool.Pool) *Store {
|
||||
return &Store{
|
||||
log: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Store) WithContext(ctx context.Context) *StoreWithContext {
|
||||
return NewStoreWithContext(ctx, db)
|
||||
}
|
||||
|
||||
func (db *Store) CreateAccount(ctx context.Context, userID, email string) (Account, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
INSERT INTO accounts (
|
||||
user_id,
|
||||
email
|
||||
)
|
||||
VALUES (
|
||||
@user_id,
|
||||
@email
|
||||
)
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING
|
||||
account_id
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"user_id": userID,
|
||||
"email": email,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
acctID, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int64])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Account{}, fmt.Errorf("account already exists: %w", consts.ErrConflict)
|
||||
}
|
||||
return Account{}, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
return Account{
|
||||
OAuthUser: OAuthUser{
|
||||
UserID: userID,
|
||||
},
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetAccount(ctx context.Context, id int64) (Account, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
"SELECT email, user_id FROM accounts WHERE account_id = @account_id",
|
||||
pgx.NamedArgs{
|
||||
"account_id": id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Email string
|
||||
User_id string
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Account{}, consts.ErrNotFound
|
||||
}
|
||||
|
||||
return Account{}, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
return Account{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: id,
|
||||
},
|
||||
OAuthUser: OAuthUser{
|
||||
r.User_id,
|
||||
},
|
||||
Email: r.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetAccountByUserID(ctx context.Context, userID string) (Account, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`SELECT
|
||||
email, account_id
|
||||
FROM
|
||||
accounts
|
||||
WHERE
|
||||
user_id = @user_id`,
|
||||
pgx.NamedArgs{
|
||||
"user_id": userID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Email string
|
||||
Account_ID int64
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Account{}, consts.ErrNotFound
|
||||
}
|
||||
|
||||
return Account{}, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
return Account{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: r.Account_ID,
|
||||
},
|
||||
OAuthUser: OAuthUser{
|
||||
UserID: userID,
|
||||
},
|
||||
Email: r.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetAccountByEmail(ctx context.Context, email string) (Account, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
"SELECT account_id, user_id FROM accounts WHERE email = @email",
|
||||
pgx.NamedArgs{
|
||||
"email": email,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Account_id int64
|
||||
User_id string
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Account{}, consts.ErrNotFound
|
||||
}
|
||||
|
||||
return Account{}, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
return Account{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: r.Account_id,
|
||||
},
|
||||
OAuthUser: OAuthUser{
|
||||
UserID: r.User_id,
|
||||
},
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetUserAndAccountByAccessToken(ctx context.Context, accessToken string) (OAuthUser, *Account, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
u.user_id,
|
||||
a.account_id,
|
||||
a.email
|
||||
FROM oauth_users u
|
||||
LEFT JOIN oauth_tokens t
|
||||
ON u.user_id = id_token_subject
|
||||
LEFT JOIN accounts a
|
||||
USING (user_id)
|
||||
WHERE access_token = @access_token
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"access_token": accessToken,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return OAuthUser{}, nil, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
User_ID string
|
||||
Account_ID *int64
|
||||
Email *string
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return OAuthUser{}, nil, consts.ErrNotFound
|
||||
}
|
||||
return OAuthUser{}, nil, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
|
||||
user := OAuthUser{UserID: r.User_ID}
|
||||
|
||||
var acct *Account
|
||||
if r.Account_ID != nil {
|
||||
acct = &Account{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: *r.Account_ID,
|
||||
},
|
||||
OAuthUser: user,
|
||||
Email: *r.Email,
|
||||
}
|
||||
}
|
||||
|
||||
return user, acct, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetShops(ctx context.Context, acctID int64) ([]AccountShop, error) {
|
||||
var shops []AccountShop
|
||||
for _, v := range getDevShops(acctID) {
|
||||
shops = append(shops, v)
|
||||
}
|
||||
|
||||
return shops, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetMockShops(ctx context.Context, acctID int64) ([]AccountShop, error) {
|
||||
infos := getAllMockShopSchemaInfos()
|
||||
lists := make([][]AccountShop, len(infos))
|
||||
numShops := 0
|
||||
|
||||
for i, in := range infos {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
SELECT
|
||||
shop_id,
|
||||
name
|
||||
FROM
|
||||
%s
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
`,
|
||||
in.shopTable,
|
||||
),
|
||||
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 string
|
||||
Name string
|
||||
}])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
list := make([]AccountShop, len(vs))
|
||||
for i, v := range vs {
|
||||
list[i] = AccountShop{
|
||||
AccountShopIDs: NewAccountIDs(acctID).
|
||||
ShopID(in.platform, v.Shop_id),
|
||||
Name: v.Name,
|
||||
}
|
||||
}
|
||||
|
||||
lists[i] = list
|
||||
numShops += len(list)
|
||||
}
|
||||
|
||||
shops := make([]AccountShop, 0, numShops)
|
||||
for _, list := range lists {
|
||||
shops = append(shops, list...)
|
||||
}
|
||||
|
||||
return shops, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]Listing, error) {
|
||||
var vs []Listing
|
||||
for _, v := range getDevListings(acctID) {
|
||||
if v.Platform != platform {
|
||||
continue
|
||||
}
|
||||
if v.ShopID != shopID {
|
||||
continue
|
||||
}
|
||||
vs = append(vs, v)
|
||||
}
|
||||
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetMockListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]Listing, error) {
|
||||
in, ok := getMockShopSchemaInfo(platform)
|
||||
if !ok {
|
||||
return nil, consts.ErrNotFound
|
||||
}
|
||||
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
SELECT
|
||||
listing_id,
|
||||
sku,
|
||||
name,
|
||||
description,
|
||||
"count"
|
||||
FROM
|
||||
%s
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND shop_id = @shop_id
|
||||
`,
|
||||
in.listingsTable,
|
||||
),
|
||||
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 {
|
||||
Listing_id string
|
||||
SKU string
|
||||
Name string
|
||||
Description string
|
||||
Count int64
|
||||
}])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
listings := make([]Listing, len(vs))
|
||||
for i, v := range vs {
|
||||
listings[i] = Listing{
|
||||
AccountShopListingIDs: NewAccountIDs(acctID).
|
||||
ShopID(in.platform, shopID).
|
||||
ListingID(v.Listing_id),
|
||||
SKU: v.SKU,
|
||||
Name: v.Name,
|
||||
Description: v.Description,
|
||||
Count: v.Count,
|
||||
}
|
||||
}
|
||||
|
||||
return listings, nil
|
||||
}
|
||||
|
||||
// additional context
|
||||
|
||||
func (db *Store) GetAccountPointerByUserID(ctx context.Context, userID string) (*Account, error) {
|
||||
acct, err := db.GetAccountByUserID(ctx, userID)
|
||||
if err == nil {
|
||||
return &acct, nil
|
||||
}
|
||||
if errors.Is(err, consts.ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package accounts
|
||||
|
||||
func getDevShops(acctID int64) []AccountShop {
|
||||
return []AccountShop{
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
Name: "Etsy 1",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
Name: "Etsy 2",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "8",
|
||||
},
|
||||
Name: "Etsy 3",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Tiktok,
|
||||
ShopID: "11",
|
||||
},
|
||||
Name: "Tiktok 1",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Tiktok,
|
||||
ShopID: "14",
|
||||
},
|
||||
Name: "Tiktok 2",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Tiktok,
|
||||
ShopID: "17",
|
||||
},
|
||||
Name: "Tiktok 3",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Wix,
|
||||
ShopID: "20",
|
||||
},
|
||||
Name: "Wix 1",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Wix,
|
||||
ShopID: "23",
|
||||
},
|
||||
Name: "Wix 2",
|
||||
},
|
||||
AccountShop{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Wix,
|
||||
ShopID: "26",
|
||||
},
|
||||
Name: "Wix 3",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getDevListings(acctID int64) []Listing {
|
||||
return []Listing{
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "1",
|
||||
},
|
||||
SKU: "sku 1",
|
||||
Name: "name 1",
|
||||
Description: "description 1",
|
||||
Count: 51,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "2",
|
||||
},
|
||||
SKU: "sku 2",
|
||||
Name: "name 2",
|
||||
Description: "description 2",
|
||||
Count: 52,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "3",
|
||||
},
|
||||
SKU: "sku 3",
|
||||
Name: "name 3",
|
||||
Description: "description 3",
|
||||
Count: 53,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "4",
|
||||
},
|
||||
SKU: "sku 4",
|
||||
Name: "name 4",
|
||||
Description: "description 4",
|
||||
Count: 54,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "5",
|
||||
},
|
||||
SKU: "sku 5",
|
||||
Name: "name 5",
|
||||
Description: "description 5",
|
||||
Count: 55,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "2",
|
||||
},
|
||||
ListingID: "6",
|
||||
},
|
||||
SKU: "sku 6",
|
||||
Name: "name 6",
|
||||
Description: "description 6",
|
||||
Count: 56,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "7",
|
||||
},
|
||||
SKU: "sku 7",
|
||||
Name: "name 7",
|
||||
Description: "description 7",
|
||||
Count: 57,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "8",
|
||||
},
|
||||
SKU: "sku 8",
|
||||
Name: "name 8",
|
||||
Description: "description 8",
|
||||
Count: 58,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "9",
|
||||
},
|
||||
SKU: "sku 9",
|
||||
Name: "name 9",
|
||||
Description: "description 9",
|
||||
Count: 59,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "10",
|
||||
},
|
||||
SKU: "sku 10",
|
||||
Name: "name 10",
|
||||
Description: "description 10",
|
||||
Count: 60,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "11",
|
||||
},
|
||||
SKU: "sku 11",
|
||||
Name: "name 11",
|
||||
Description: "description 11",
|
||||
Count: 61,
|
||||
},
|
||||
{
|
||||
AccountShopListingIDs: AccountShopListingIDs{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: Etsy,
|
||||
ShopID: "5",
|
||||
},
|
||||
ListingID: "12",
|
||||
},
|
||||
SKU: "sku 12",
|
||||
Name: "name 12",
|
||||
Description: "description 12",
|
||||
Count: 62,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"ruben/inventory2/consts"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
"count" = @count
|
||||
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,
|
||||
"count": listing.Count,
|
||||
},
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: will have to be updated once we start creating mock sync groups
|
||||
func (db *Store) DeleteMockListing(ctx context.Context, ids AccountShopListingIDs) error {
|
||||
_, listingTableName, err := getMockShopAndListingsTableNames(ids.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tag, err := db.db.Exec(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
DELETE FROM
|
||||
%s
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND shop_id = @shop_id
|
||||
AND listing_id = @listing_id
|
||||
`,
|
||||
listingTableName,
|
||||
),
|
||||
pgx.NamedArgs{
|
||||
"account_id": ids.AccountID,
|
||||
"shop_id": ids.ShopID,
|
||||
"listing_id": ids.ListingID,
|
||||
},
|
||||
)
|
||||
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(
|
||||
`
|
||||
SELECT
|
||||
true AS shop_exists,
|
||||
l.listing_id,
|
||||
l.sku,
|
||||
l.name,
|
||||
l.description,
|
||||
l."count"
|
||||
FROM
|
||||
%s AS s
|
||||
LEFT JOIN
|
||||
%s AS l
|
||||
USING
|
||||
(account_id, shop_id)
|
||||
WHERE
|
||||
(account_id IS NULL OR account_id = @account_id)
|
||||
AND (shop_id IS NULL OR shop_id = @shop_id)
|
||||
ORDER BY
|
||||
name
|
||||
`,
|
||||
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 {
|
||||
return nil, fmt.Errorf("%w: shop not found", consts.ErrNotFound)
|
||||
}
|
||||
if !vs[0].SKU.Valid {
|
||||
// shop exists, but there are not listings
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
type mockShopSchemaInfo struct {
|
||||
platform Platform
|
||||
shopTable string
|
||||
listingsTable string
|
||||
syncGroupListingDraftsTable string
|
||||
}
|
||||
|
||||
func getMockShopSchemaInfo(platform Platform) (mockShopSchemaInfo, bool) {
|
||||
for _, in := range getAllMockShopSchemaInfos() {
|
||||
if in.platform == platform {
|
||||
return in, true
|
||||
}
|
||||
}
|
||||
|
||||
return mockShopSchemaInfo{}, false
|
||||
}
|
||||
|
||||
func getAllMockShopSchemaInfos() []mockShopSchemaInfo {
|
||||
return []mockShopSchemaInfo{
|
||||
{
|
||||
platform: Amazon,
|
||||
shopTable: "mock.shop_amazon",
|
||||
listingsTable: "mock.shop_amazon_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_amazon_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: BigCartel,
|
||||
shopTable: "mock.shop_big_cartel",
|
||||
listingsTable: "mock.shop_big_cartel_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_big_cartel_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Ebay,
|
||||
shopTable: "mock.shop_ebay",
|
||||
listingsTable: "mock.shop_ebay_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_ebay_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Ecwid,
|
||||
shopTable: "mock.shop_ecwid",
|
||||
listingsTable: "mock.shop_ecwid_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_ecwid_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Etsy,
|
||||
shopTable: "mock.shop_etsy",
|
||||
listingsTable: "mock.shop_etsy_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_etsy_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Shopify,
|
||||
shopTable: "mock.shop_shopify",
|
||||
listingsTable: "mock.shop_shopify_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_shopify_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: SquareOnline,
|
||||
shopTable: "mock.shop_square_online",
|
||||
listingsTable: "mock.shop_square_online_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_square_online_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Squarespace,
|
||||
shopTable: "mock.shop_squarespace",
|
||||
listingsTable: "mock.shop_squarespace_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_squarespace_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Tiktok,
|
||||
shopTable: "mock.shop_tiktok",
|
||||
listingsTable: "mock.shop_tiktok_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_tiktok_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: WalmartMarketplace,
|
||||
shopTable: "mock.shop_walmart_marketplace",
|
||||
listingsTable: "mock.shop_walmart_marketplace_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_walmart_marketplace_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Wix,
|
||||
shopTable: "mock.shop_wix",
|
||||
listingsTable: "mock.shop_wix_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_wix_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: WooCommerce,
|
||||
shopTable: "mock.shop_woo_commerce",
|
||||
listingsTable: "mock.shop_woo_commerce_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_woo_commerce_sync_group_listing_drafts",
|
||||
},
|
||||
{
|
||||
platform: Zoho,
|
||||
shopTable: "mock.shop_zoho",
|
||||
listingsTable: "mock.shop_zoho_listings",
|
||||
syncGroupListingDraftsTable: "mock.shop_zoho_sync_group_listing_drafts",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getMockShopAndListingsTableNames(platform Platform) (shop string, listing string, err error) {
|
||||
shop, err = getMockShopTableName(platform)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return shop, shop + "_listings", nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"ruben/inventory2/consts"
|
||||
)
|
||||
|
||||
// TODO: simpify, if possible (single query ideal)
|
||||
func (db *Store) SetOrderOfPlatformOnAccountPage(
|
||||
ctx context.Context,
|
||||
acctID int64,
|
||||
platform Platform,
|
||||
orderIndex int,
|
||||
) (orderedPlatforms []Platform, prevOrderIndex int, err error) {
|
||||
tx, err := db.db.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
// look up specified indexes
|
||||
|
||||
rows, err := tx.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
platform,
|
||||
order_index
|
||||
FROM
|
||||
accounts_page_platform_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
ORDER BY
|
||||
order_index ASC
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to perform query to look up existing indexes: %w", err)
|
||||
}
|
||||
|
||||
indexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
|
||||
Platform Platform
|
||||
Order_index int
|
||||
}])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to scan rows for query to look up existing indexes: %w", err)
|
||||
}
|
||||
|
||||
// compute the implied indexes,
|
||||
// and construct the full sequence of platforms
|
||||
|
||||
indexPerPlatform := make(map[Platform]int, len(allPlatforms))
|
||||
platformPerIndex := make(map[int]Platform, len(allPlatforms))
|
||||
for _, v := range indexes {
|
||||
indexPerPlatform[v.Platform] = v.Order_index
|
||||
platformPerIndex[v.Order_index] = v.Platform
|
||||
}
|
||||
|
||||
prevIndex := -1
|
||||
for _, p := range allPlatforms {
|
||||
if _, indexSet := indexPerPlatform[p]; !indexSet {
|
||||
index := prevIndex
|
||||
for indexUsed := true; indexUsed; _, indexUsed = platformPerIndex[index] {
|
||||
index += 1
|
||||
}
|
||||
prevIndex = index
|
||||
|
||||
indexPerPlatform[p] = index
|
||||
platformPerIndex[index] = p
|
||||
}
|
||||
}
|
||||
|
||||
prevOrderIndex = indexPerPlatform[platform]
|
||||
orderedPlatforms = make([]Platform, len(allPlatforms))
|
||||
if indexPerPlatform[platform] == orderIndex {
|
||||
for i := range allPlatforms {
|
||||
orderedPlatforms[i] = platformPerIndex[i]
|
||||
}
|
||||
return orderedPlatforms, prevOrderIndex, nil
|
||||
}
|
||||
|
||||
if increased := orderIndex > prevOrderIndex; increased {
|
||||
// decrement in indexes between the previous index and new index
|
||||
for index := prevOrderIndex + 1; index <= orderIndex; index += 1 {
|
||||
p := platformPerIndex[index]
|
||||
indexPerPlatform[p] = index - 1
|
||||
platformPerIndex[index-1] = p
|
||||
}
|
||||
indexPerPlatform[platform] = orderIndex
|
||||
platformPerIndex[orderIndex] = platform
|
||||
} else {
|
||||
// increment in indexes between the previous index and new index
|
||||
for index := prevOrderIndex - 1; index >= orderIndex; index -= 1 {
|
||||
p := platformPerIndex[index]
|
||||
indexPerPlatform[p] = index + 1
|
||||
platformPerIndex[index+1] = p
|
||||
}
|
||||
indexPerPlatform[platform] = orderIndex
|
||||
platformPerIndex[orderIndex] = platform
|
||||
}
|
||||
|
||||
// delete all indexes for the acct in the db,
|
||||
// then insert all updated indexes
|
||||
|
||||
valuesLines := make([]string, len(allPlatforms))
|
||||
args := pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
}
|
||||
for i, p := range allPlatforms {
|
||||
valuesLines[i] = fmt.Sprintf("(@account_id, @platform_%d, @order_index_%d::smallint)", i, i)
|
||||
args[fmt.Sprintf("platform_%d", i)] = p
|
||||
args[fmt.Sprintf("order_index_%d", i)] = indexPerPlatform[p]
|
||||
orderedPlatforms[i] = platformPerIndex[i]
|
||||
}
|
||||
|
||||
_, err = tx.Exec(
|
||||
ctx,
|
||||
fmt.Sprintf(`
|
||||
WITH deleted_indexes AS (
|
||||
DELETE FROM
|
||||
accounts_page_platform_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
RETURNING
|
||||
account_id
|
||||
), new_indexes(account_id, platform, order_index) AS (
|
||||
SELECT DISTINCT
|
||||
x.account_id, x.platform, x.order_index
|
||||
FROM
|
||||
(VALUES %s) AS x(account_id, platform, order_index)
|
||||
LEFT JOIN
|
||||
deleted_indexes
|
||||
ON
|
||||
x.account_id = deleted_indexes.account_id
|
||||
)
|
||||
INSERT INTO
|
||||
accounts_page_platform_order_indexes (
|
||||
account_id,
|
||||
platform,
|
||||
order_index
|
||||
)
|
||||
SELECT
|
||||
account_id,
|
||||
platform::Platform,
|
||||
order_index
|
||||
FROM
|
||||
new_indexes
|
||||
`, strings.Join(valuesLines, ", ")),
|
||||
args,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to perform query to delete old indexes and insert new indexes: %w", err)
|
||||
}
|
||||
|
||||
if tx.Commit(ctx); err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to commit txn: %w", err)
|
||||
}
|
||||
|
||||
return orderedPlatforms, prevOrderIndex, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetOrderOfPlatformsOnAccountPage(ctx context.Context, acctID int64) ([]Platform, error) {
|
||||
// get specified order indexes per platform
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
platform,
|
||||
order_index
|
||||
FROM
|
||||
accounts_page_platform_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Platform Platform
|
||||
Order_index int
|
||||
}
|
||||
|
||||
platformsWithIndexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
|
||||
Platform Platform
|
||||
Order_index int
|
||||
}])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
// hash the specified platforms by index
|
||||
|
||||
platformsByIndex := make(map[int]Platform, len(platformsWithIndexes))
|
||||
for _, v := range platformsWithIndexes {
|
||||
platformsByIndex[v.Order_index] = v.Platform
|
||||
}
|
||||
|
||||
// form a sorted list of platforms without indexes specified, sorted alphabetically
|
||||
|
||||
allPlatformsByName := make(map[Platform]struct{}, len(allPlatforms))
|
||||
for _, p := range allPlatforms {
|
||||
allPlatformsByName[p] = struct{}{}
|
||||
}
|
||||
for _, v := range platformsWithIndexes {
|
||||
delete(allPlatformsByName, v.Platform)
|
||||
}
|
||||
platformsWithoutAnIndexSpecified := make([]Platform, 0, len(allPlatformsByName))
|
||||
for p := range allPlatformsByName {
|
||||
platformsWithoutAnIndexSpecified = append(platformsWithoutAnIndexSpecified, p)
|
||||
}
|
||||
slices.SortFunc(platformsWithoutAnIndexSpecified, func(a, b Platform) int {
|
||||
al := strings.ToLower(string(a))
|
||||
bl := strings.ToLower(string(b))
|
||||
if al < bl {
|
||||
return -1
|
||||
}
|
||||
if bl < al {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
// construct the list of the ordered platforms
|
||||
|
||||
res := make([]Platform, len(allPlatforms))
|
||||
nextIndex := 0
|
||||
for i := range res {
|
||||
if p, ok := platformsByIndex[i]; ok {
|
||||
res[i] = p
|
||||
} else {
|
||||
res[i] = platformsWithoutAnIndexSpecified[nextIndex]
|
||||
nextIndex += 1
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform) (int, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
order_index
|
||||
FROM
|
||||
accounts_page_platform_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND platform = @platform
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"platform": platform,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
i, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, consts.ErrNotFound
|
||||
}
|
||||
return 0, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
Platform string
|
||||
)
|
||||
|
||||
const (
|
||||
Amazon Platform = "amazon"
|
||||
BigCartel Platform = "big_cartel"
|
||||
Ebay Platform = "ebay"
|
||||
Ecwid Platform = "ecwid"
|
||||
Etsy Platform = "Etsy"
|
||||
Shopify Platform = "shopify"
|
||||
SquareOnline Platform = "square_online"
|
||||
Squarespace Platform = "squarespace"
|
||||
Tiktok Platform = "Tiktok"
|
||||
WalmartMarketplace Platform = "walmart_marketplace"
|
||||
Wix Platform = "Wix"
|
||||
WooCommerce Platform = "woo_commerce"
|
||||
Zoho Platform = "zoho"
|
||||
)
|
||||
|
||||
var (
|
||||
allPlatforms = []Platform{
|
||||
Amazon,
|
||||
BigCartel,
|
||||
Ebay,
|
||||
Ecwid,
|
||||
Etsy,
|
||||
Shopify,
|
||||
SquareOnline,
|
||||
Squarespace,
|
||||
Tiktok,
|
||||
WalmartMarketplace,
|
||||
Wix,
|
||||
WooCommerce,
|
||||
Zoho,
|
||||
}
|
||||
)
|
||||
|
||||
func NewPlatform(s string) (Platform, error) {
|
||||
for _, v := range allPlatforms {
|
||||
if strings.ToLower(s) == strings.ToLower(string(v)) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("unrecognized constant: %q", s)
|
||||
}
|
||||
|
||||
func (p Platform) PrettyPrint() string {
|
||||
switch p {
|
||||
case Etsy:
|
||||
return "Etsy"
|
||||
case Tiktok:
|
||||
return "Tiktok"
|
||||
case Wix:
|
||||
return "Wix"
|
||||
case Ebay:
|
||||
return "Ebay"
|
||||
case WalmartMarketplace:
|
||||
return "Walmart Marketplace"
|
||||
case Amazon:
|
||||
return "Amazon"
|
||||
case BigCartel:
|
||||
return "Big Cartel"
|
||||
case Ecwid:
|
||||
return "Ecwid"
|
||||
case Zoho:
|
||||
return "Zoho"
|
||||
case SquareOnline:
|
||||
return "Square Online"
|
||||
case Squarespace:
|
||||
return "Squarespace"
|
||||
case WooCommerce:
|
||||
return "Woo Commerce"
|
||||
case Shopify:
|
||||
return "Shopify"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// sql.Scanner implementation
|
||||
func (p *Platform) Scan(src any) error {
|
||||
var s string
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
s = v
|
||||
case byte:
|
||||
s = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %T: %v", src, src)
|
||||
}
|
||||
|
||||
dst, err := NewPlatform(s)
|
||||
if err == nil {
|
||||
*p = dst
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// sql/driver.Valuer implementation
|
||||
func (p Platform) Value() (any, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Code generated by concurry DO NOT EDIT.
|
||||
// https://github.com/angelbeltran/concurry
|
||||
// concurry
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type StoreWithContext struct {
|
||||
ctx context.Context
|
||||
*Store
|
||||
}
|
||||
|
||||
func NewStoreWithContext(ctx context.Context, v *Store) *StoreWithContext {
|
||||
return &StoreWithContext{
|
||||
ctx: ctx,
|
||||
Store: v,
|
||||
}
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) CreateAccount(userID string, email string) (Account, error) {
|
||||
return v_ctx.Store.CreateAccount(v_ctx.ctx, userID, email)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetAccount(id int64) (Account, error) {
|
||||
return v_ctx.Store.GetAccount(v_ctx.ctx, id)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetAccountByUserID(userID string) (Account, error) {
|
||||
return v_ctx.Store.GetAccountByUserID(v_ctx.ctx, userID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetAccountByEmail(email string) (Account, error) {
|
||||
return v_ctx.Store.GetAccountByEmail(v_ctx.ctx, email)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetUserAndAccountByAccessToken(accessToken string) (OAuthUser, *Account, error) {
|
||||
return v_ctx.Store.GetUserAndAccountByAccessToken(v_ctx.ctx, accessToken)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetShops(acctID int64) ([]AccountShop, error) {
|
||||
return v_ctx.Store.GetShops(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetMockShops(acctID int64) ([]AccountShop, error) {
|
||||
return v_ctx.Store.GetMockShops(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetListingsForShop(acctID int64, platform Platform, shopID string) ([]Listing, error) {
|
||||
return v_ctx.Store.GetListingsForShop(v_ctx.ctx, acctID, platform, shopID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetMockListingsForShop(acctID int64, platform Platform, shopID string) ([]Listing, error) {
|
||||
return v_ctx.Store.GetMockListingsForShop(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)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) ListMockShopsForPlatform(acctID int64, platform Platform) ([]MockShop, error) {
|
||||
return v_ctx.Store.ListMockShopsForPlatform(v_ctx.ctx, acctID, platform)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetMockShop(acctID int64, platform Platform, shopID string) (*MockShop, error) {
|
||||
return v_ctx.Store.GetMockShop(v_ctx.ctx, acctID, platform, shopID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) CreateMockListing(listing MockListing) (MockListing, error) {
|
||||
return v_ctx.Store.CreateMockListing(v_ctx.ctx, listing)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) UpdateMockListing(listing MockListing) error {
|
||||
return v_ctx.Store.UpdateMockListing(v_ctx.ctx, listing)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) DeleteMockListing(ids AccountShopListingIDs) error {
|
||||
return v_ctx.Store.DeleteMockListing(v_ctx.ctx, ids)
|
||||
}
|
||||
|
||||
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) {
|
||||
return v_ctx.Store.SetOrderOfPlatformOnAccountPage(v_ctx.ctx, acctID, platform, orderIndex)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetOrderOfPlatformsOnAccountPage(acctID int64) ([]Platform, error) {
|
||||
return v_ctx.Store.GetOrderOfPlatformsOnAccountPage(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetOrderOfPlatformOnAccountPage(acctID int64, platform Platform) (int, error) {
|
||||
return v_ctx.Store.GetOrderOfPlatformOnAccountPage(v_ctx.ctx, acctID, platform)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) CreateSyncGroupListingDraft(acctID int64) (int, error) {
|
||||
return v_ctx.Store.CreateSyncGroupListingDraft(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) CreateMockSyncGroupListingDraft(acctID int64) (int, error) {
|
||||
return v_ctx.Store.CreateMockSyncGroupListingDraft(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetSyncGroupListingDraft(acctID int64, orderIndex int) (SyncGroupListingDraft, error) {
|
||||
return v_ctx.Store.GetSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetMockSyncGroupListingDraft(acctID int64, orderIndex int) (SyncGroupListingDraft, error) {
|
||||
return v_ctx.Store.GetMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) SetShopInSyncGroupListingDraft(acctID int64, orderIndex int, platform Platform, shopID string) error {
|
||||
return v_ctx.Store.SetShopInSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, platform, shopID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) SetShopInMockSyncGroupListingDraft(acctID int64, orderIndex int, platform Platform, shopID string) error {
|
||||
return v_ctx.Store.SetShopInMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, platform, shopID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) SetListingInSyncGroupListingDraft(acctID int64, orderIndex int, listingID string) error {
|
||||
return v_ctx.Store.SetListingInSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, listingID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) SetListingInMockSyncGroupListingDraft(acctID int64, orderIndex int, listingID string) error {
|
||||
return v_ctx.Store.SetListingInMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, listingID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) DeleteSyncGroupListingDraft(acctID int64, orderIndex int) (int, error) {
|
||||
return v_ctx.Store.DeleteSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) DeleteMockSyncGroupListingDraft(acctID int64, orderIndex int) error {
|
||||
return v_ctx.Store.DeleteMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetSyncGroupListingDrafts(acctID int64) ([]SyncGroupListingDraft, error) {
|
||||
return v_ctx.Store.GetSyncGroupListingDrafts(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) GetMockSyncGroupListingDrafts(acctID int64) ([]SyncGroupListingDraft, error) {
|
||||
return v_ctx.Store.GetMockSyncGroupListingDrafts(v_ctx.ctx, acctID)
|
||||
}
|
||||
|
||||
func (v_ctx *StoreWithContext) SaveNewSyncGroup(acctID int64) (SyncGroup, error) {
|
||||
return v_ctx.Store.SaveNewSyncGroup(v_ctx.ctx, acctID)
|
||||
}
|
||||
@@ -0,0 +1,820 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"ruben/inventory2/consts"
|
||||
)
|
||||
|
||||
type (
|
||||
SyncGroup struct {
|
||||
SyncGroupIDs
|
||||
Listings []SyncGroupListing
|
||||
}
|
||||
|
||||
SyncGroupListing struct {
|
||||
SyncGroupIDs
|
||||
AccountShopIDs
|
||||
ListingID string
|
||||
OrderIndex int
|
||||
}
|
||||
|
||||
SyncGroupIDs struct {
|
||||
AccountIDs
|
||||
SyncGroupID int64
|
||||
}
|
||||
|
||||
// TODO: the id ACTUALLY is the account_id and order_index
|
||||
SyncGroupListingDraft struct {
|
||||
AccountShopIDs
|
||||
ListingID string
|
||||
OrderIndex int
|
||||
}
|
||||
)
|
||||
|
||||
func (db *Store) CreateSyncGroupListingDraft(ctx context.Context, acctID int64) (orderIndex int, err error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
WITH new_order_index AS (
|
||||
SELECT
|
||||
COALESCE(MAX(order_index), -1) + 1 AS order_index
|
||||
FROM
|
||||
sync_group_listing_drafts
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
)
|
||||
INSERT INTO
|
||||
sync_group_listing_drafts (
|
||||
account_id,
|
||||
order_index
|
||||
)
|
||||
SELECT
|
||||
@account_id,
|
||||
order_index
|
||||
FROM
|
||||
new_order_index
|
||||
RETURNING
|
||||
order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
if orderIndex, err = pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]); err != nil {
|
||||
return 0, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
return orderIndex, nil
|
||||
}
|
||||
|
||||
func (db *Store) CreateMockSyncGroupListingDraft(ctx context.Context, acctID int64) (orderIndex int, err error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
WITH max_order_index(max_index) AS (
|
||||
SELECT
|
||||
MAX(order_index) AS max_index
|
||||
FROM
|
||||
mock.sync_group_listing_draft_order_indexes
|
||||
)
|
||||
INSERT INTO
|
||||
mock.sync_group_listing_draft_order_indexes (
|
||||
account_id,
|
||||
order_index
|
||||
)
|
||||
SELECT
|
||||
@account_id,
|
||||
COALESCE(max_index, -1) + 1
|
||||
FROM
|
||||
max_order_index
|
||||
RETURNING
|
||||
order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
if orderIndex, err = pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]); err != nil {
|
||||
return 0, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
return orderIndex, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (SyncGroupListingDraft, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
FROM
|
||||
sync_group_listing_drafts
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[struct {
|
||||
Platform *Platform
|
||||
Shop_id *string
|
||||
Listing_id *string
|
||||
Order_index *int
|
||||
}])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return SyncGroupListingDraft{}, consts.ErrNotFound
|
||||
}
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
return SyncGroupListingDraft{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: deref(r.Platform),
|
||||
ShopID: deref(r.Shop_id),
|
||||
},
|
||||
ListingID: deref(r.Listing_id),
|
||||
OrderIndex: orderIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (SyncGroupListingDraft, error) {
|
||||
tx, err := db.db.BeginTx(ctx, pgx.TxOptions{
|
||||
AccessMode: pgx.ReadOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
rows, err := tx.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
true
|
||||
FROM
|
||||
mock.sync_group_listing_draft_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
if _, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[bool]); err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
for _, info := range getAllMockShopSchemaInfos() {
|
||||
rows, err := tx.Query(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
SELECT
|
||||
shop_id,
|
||||
listing_id
|
||||
FROM
|
||||
%s
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
info.syncGroupListingDraftsTable,
|
||||
),
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
v, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[struct {
|
||||
Shop_id pgtype.Text
|
||||
Listing_id pgtype.Text
|
||||
}])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("faild to scan row: %w", err)
|
||||
}
|
||||
|
||||
return SyncGroupListingDraft{
|
||||
AccountShopIDs: NewAccountIDs(acctID).
|
||||
ShopID(info.platform, v.Shop_id.String),
|
||||
ListingID: v.Listing_id.String,
|
||||
OrderIndex: orderIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return SyncGroupListingDraft{}, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return SyncGroupListingDraft{
|
||||
AccountShopIDs: NewAccountIDs(acctID).
|
||||
ShopID("", ""),
|
||||
OrderIndex: orderIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Store) SetShopInSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, platform Platform, shopID string) error {
|
||||
tag, err := db.db.Exec(
|
||||
ctx,
|
||||
`
|
||||
UPDATE
|
||||
sync_group_listing_drafts
|
||||
SET
|
||||
platform = @platform,
|
||||
shop_id = @shop_id,
|
||||
listing_id = NULL
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
"platform": platform,
|
||||
"shop_id": shopID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() != 1 {
|
||||
return consts.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Store) SetShopInMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, platform Platform, shopID string) error {
|
||||
info, ok := getMockShopSchemaInfo(platform)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: unrecognized platform: %s", consts.ErrNotFound, platform)
|
||||
}
|
||||
|
||||
tx, err := db.db.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
_, err = tx.Exec(
|
||||
ctx,
|
||||
`
|
||||
DELETE FROM
|
||||
mock.sync_group_listing_draft_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
RETURNING
|
||||
account_id,
|
||||
order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query deleting existing index: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(
|
||||
ctx,
|
||||
`
|
||||
INSERT INTO
|
||||
mock.sync_group_listing_draft_order_indexes (
|
||||
account_id,
|
||||
order_index
|
||||
)
|
||||
VALUES (
|
||||
@account_id,
|
||||
@order_index
|
||||
)
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// TODO: handle error where account doesn't exist?
|
||||
return fmt.Errorf("failed to perform query inserting new index: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
INSERT INTO
|
||||
%s (
|
||||
account_id,
|
||||
order_index,
|
||||
shop_id
|
||||
)
|
||||
VALUES (
|
||||
@account_id,
|
||||
@order_index,
|
||||
@shop_id
|
||||
)
|
||||
`,
|
||||
info.syncGroupListingDraftsTable,
|
||||
),
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
"shop_id": shopID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query inserting specific shop index: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Store) SetListingInSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, listingID string) error {
|
||||
tag, err := db.db.Exec(
|
||||
ctx,
|
||||
`
|
||||
UPDATE
|
||||
sync_group_listing_drafts
|
||||
SET
|
||||
listing_id = @listing_id
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
"listing_id": listingID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() != 1 {
|
||||
return consts.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Store) SetListingInMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, listingID string) error {
|
||||
for _, info := range getAllMockShopSchemaInfos() {
|
||||
tag, err := db.db.Exec(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
UPDATE
|
||||
%s
|
||||
SET
|
||||
listing_id = @listing_id
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
info.syncGroupListingDraftsTable,
|
||||
),
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
"listing_id": listingID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
if tag.RowsAffected() > 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return consts.ErrNotFound
|
||||
}
|
||||
|
||||
func (db *Store) DeleteSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (numOfRows int, err error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
WITH deleted_draft AS (
|
||||
DELETE FROM
|
||||
sync_group_listing_drafts
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
RETURNING
|
||||
true AS found
|
||||
)
|
||||
SELECT
|
||||
COUNT(*) as num_rows,
|
||||
COALESCE(dd.found, FALSE) as found
|
||||
FROM
|
||||
sync_group_listing_drafts ld
|
||||
LEFT JOIN
|
||||
deleted_draft dd
|
||||
ON TRUE
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
GROUP BY
|
||||
ld.account_id, dd.found
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Found bool
|
||||
Num_rows int
|
||||
}
|
||||
|
||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, consts.ErrNotFound
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
if !r.Found {
|
||||
return 0, consts.ErrNotFound
|
||||
}
|
||||
|
||||
return r.Num_rows, nil
|
||||
}
|
||||
|
||||
func (db *Store) DeleteMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) error {
|
||||
tag, err := db.db.Exec(
|
||||
ctx,
|
||||
`
|
||||
DELETE FROM
|
||||
mock.sync_group_listing_draft_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
AND order_index = @order_index
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
"order_index": orderIndex,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() != 1 {
|
||||
return consts.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([]SyncGroupListingDraft, error) {
|
||||
rows, err := db.db.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
order_index,
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
FROM
|
||||
sync_group_listing_drafts
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
ORDER BY
|
||||
order_index ASC
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Order_index int
|
||||
Platform *Platform
|
||||
Shop_id *string
|
||||
Listing_id *string
|
||||
}
|
||||
|
||||
rs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[Row])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
listings := make([]SyncGroupListingDraft, len(rs))
|
||||
for i, r := range rs {
|
||||
listings[i] = SyncGroupListingDraft{
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: deref(r.Platform),
|
||||
ShopID: deref(r.Shop_id),
|
||||
},
|
||||
ListingID: deref(r.Listing_id),
|
||||
OrderIndex: r.Order_index,
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(listings, func(a, b SyncGroupListingDraft) int {
|
||||
return a.OrderIndex - b.OrderIndex
|
||||
})
|
||||
|
||||
return listings, nil
|
||||
}
|
||||
|
||||
func (db *Store) GetMockSyncGroupListingDrafts(ctx context.Context, acctID int64) ([]SyncGroupListingDraft, error) {
|
||||
tx, err := db.db.BeginTx(ctx, pgx.TxOptions{
|
||||
AccessMode: pgx.ReadOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
rows, err := tx.Query(
|
||||
ctx,
|
||||
`
|
||||
SELECT
|
||||
order_index
|
||||
FROM
|
||||
mock.sync_group_listing_draft_order_indexes
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
indexes, err := pgx.CollectRows(rows, pgx.RowTo[int])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
listingsByOrderIndex := make(map[int]SyncGroupListingDraft, len(indexes))
|
||||
for _, n := range indexes {
|
||||
listingsByOrderIndex[n] = SyncGroupListingDraft{
|
||||
AccountShopIDs: NewAccountIDs(acctID).
|
||||
ShopID("", ""),
|
||||
OrderIndex: n,
|
||||
}
|
||||
}
|
||||
|
||||
for _, info := range getAllMockShopSchemaInfos() {
|
||||
rows, err := tx.Query(
|
||||
ctx,
|
||||
fmt.Sprintf(
|
||||
`
|
||||
SELECT
|
||||
shop_id,
|
||||
order_index,
|
||||
listing_id
|
||||
FROM
|
||||
%s
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
`,
|
||||
info.syncGroupListingDraftsTable,
|
||||
),
|
||||
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 pgtype.Text
|
||||
Order_index pgtype.Int8
|
||||
Listing_id pgtype.Text
|
||||
}])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
for _, v := range vs {
|
||||
n := int(v.Order_index.Int64)
|
||||
l := listingsByOrderIndex[n]
|
||||
l.Platform = info.platform
|
||||
l.ShopID = v.Shop_id.String
|
||||
l.ListingID = v.Listing_id.String
|
||||
listingsByOrderIndex[n] = l
|
||||
}
|
||||
}
|
||||
|
||||
listings := make([]SyncGroupListingDraft, 0, len(listingsByOrderIndex))
|
||||
for _, v := range listingsByOrderIndex {
|
||||
listings = append(listings, v)
|
||||
}
|
||||
slices.SortFunc(listings, func(a, b SyncGroupListingDraft) int {
|
||||
return a.OrderIndex - b.OrderIndex
|
||||
})
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return listings, nil
|
||||
}
|
||||
|
||||
func (db *Store) SaveNewSyncGroup(ctx context.Context, acctID int64) (SyncGroup, error) {
|
||||
txn, err := db.db.Begin(ctx)
|
||||
if err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer txn.Rollback(ctx)
|
||||
|
||||
rows, err := txn.Query(
|
||||
ctx,
|
||||
"SELECT COUNT(*) FROM sync_group_listing_drafts WHERE account_id = @account_id",
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
numDrafts, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int])
|
||||
if err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
if numDrafts < 2 {
|
||||
return SyncGroup{}, fmt.Errorf("%w: insufficient listings: must be at least 2: %d", consts.ErrConflict, numDrafts)
|
||||
}
|
||||
|
||||
rows, err = txn.Query(
|
||||
ctx,
|
||||
`
|
||||
WITH deleted_sync_group_listing_drafts AS (
|
||||
DELETE FROM
|
||||
sync_group_listing_drafts
|
||||
WHERE
|
||||
account_id = @account_id
|
||||
RETURNING
|
||||
order_index,
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
), new_sync_group AS (
|
||||
INSERT INTO
|
||||
sync_groups (
|
||||
account_id
|
||||
)
|
||||
VALUES (
|
||||
@account_id
|
||||
)
|
||||
RETURNING
|
||||
sync_group_id
|
||||
)
|
||||
INSERT INTO
|
||||
sync_groups_listings (
|
||||
sync_group_id,
|
||||
order_index,
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
)
|
||||
SELECT
|
||||
sync_group_id,
|
||||
order_index,
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
FROM
|
||||
new_sync_group
|
||||
JOIN
|
||||
deleted_sync_group_listing_drafts
|
||||
ON
|
||||
TRUE
|
||||
RETURNING
|
||||
sync_group_id,
|
||||
order_index,
|
||||
platform,
|
||||
shop_id,
|
||||
listing_id
|
||||
`,
|
||||
pgx.NamedArgs{
|
||||
"account_id": acctID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to perform query: %w", err)
|
||||
}
|
||||
|
||||
rs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
|
||||
Sync_group_id int64
|
||||
Order_index int
|
||||
Platform Platform
|
||||
Shop_id string
|
||||
Listing_id string
|
||||
}])
|
||||
if err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to scan rows: %w", err)
|
||||
}
|
||||
|
||||
if err := txn.Commit(ctx); err != nil {
|
||||
return SyncGroup{}, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
listings := make([]SyncGroupListing, len(rs))
|
||||
for i, r := range rs {
|
||||
listings[i] = SyncGroupListing{
|
||||
SyncGroupIDs: SyncGroupIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
SyncGroupID: r.Sync_group_id,
|
||||
},
|
||||
AccountShopIDs: AccountShopIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
Platform: r.Platform,
|
||||
ShopID: r.Shop_id,
|
||||
},
|
||||
ListingID: r.Listing_id,
|
||||
OrderIndex: r.Order_index,
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(listings, func(a, b SyncGroupListing) int {
|
||||
return a.OrderIndex - b.OrderIndex
|
||||
})
|
||||
|
||||
return SyncGroup{
|
||||
SyncGroupIDs: SyncGroupIDs{
|
||||
AccountIDs: AccountIDs{
|
||||
AccountID: acctID,
|
||||
},
|
||||
SyncGroupID: listings[0].SyncGroupID,
|
||||
},
|
||||
Listings: listings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deref[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
Reference in New Issue
Block a user