package reports import ( "context" "fmt" "slices" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "ruben/inventory2/consts" "ruben/inventory2/domains/accounts" "ruben/inventory2/logging" ) //go:generate concurry -s Store type ( Store struct { log *logging.Logger db *pgxpool.Pool accts *accounts.Store } ) func NewStore(logger *logging.Logger, db *pgxpool.Pool, accts *accounts.Store) *Store { return &Store{ log: logger, db: db, accts: accts, } } func (db *Store) WithContext(ctx context.Context) *StoreWithContext { return NewStoreWithContext(ctx, db) } // TODO: prefix all these names with 'mock' since they're all mock stuff type ( ListingCountsOverTimeReport struct { AccountID int64 Platform accounts.Platform ShopID string ListingID string Counts []ListingCountAtTime } ListingCountAtTime struct { EventTimestamp *time.Time Count int64 } ) func (r ListingCountsOverTimeReport) MaxCount() ListingCountAtTime { if len(r.Counts) == 0 { return ListingCountAtTime{} } return slices.MaxFunc(r.Counts, func(a, b ListingCountAtTime) int { return int(a.Count - b.Count) }) } func (r ListingCountsOverTimeReport) MinCount() ListingCountAtTime { if len(r.Counts) == 0 { return ListingCountAtTime{} } return slices.MinFunc(r.Counts, func(a, b ListingCountAtTime) int { return int(a.Count - b.Count) }) } func (db *Store) GetListingCountsReport(ctx context.Context, acctID int64, platform accounts.Platform, shopID, listingID string) (*ListingCountsOverTimeReport, error) { counts, err := db.GetListingCountsOverTime(ctx, acctID, platform, shopID, listingID) if err != nil { return nil, err } return &ListingCountsOverTimeReport{ AccountID: acctID, Platform: platform, ShopID: shopID, ListingID: listingID, Counts: counts, }, nil } func (db *Store) GetListingCountsOverTime(ctx context.Context, acctID int64, platform accounts.Platform, shopID, listingID string) ([]ListingCountAtTime, error) { info, ok := getMockShopSchemaInfo(platform) if !ok { return nil, consts.ErrNotFound } var counts []ListingCountAtTime err := pgx.BeginTxFunc(ctx, db.db, pgx.TxOptions{ AccessMode: pgx.ReadOnly, }, func(tx pgx.Tx) error { rows, err := tx.Query( ctx, fmt.Sprintf( ` SELECT 1 FROM %s WHERE shop_id = @shop_id AND listing_id = @listing_id `, info.listingsTable, ), pgx.NamedArgs{ "shop_id": shopID, "listing_id": listingID, }, ) if err != nil { return fmt.Errorf("failed to perform query to check the existing of the listing: %w", err) } if _, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]); err != nil { return fmt.Errorf("listing not found: %w", consts.ErrNotFound) } rows, err = tx.Query( ctx, fmt.Sprintf( ` SELECT event_timestamp AS eventTimestamp, "count" FROM %s WHERE shop_id = @shop_id AND listing_id = @listing_id `, info.listingCountsView, ), pgx.NamedArgs{ "shop_id": shopID, "listing_id": listingID, }, ) if err != nil { return fmt.Errorf("failed to perform query: %w", err) } if counts, err = pgx.CollectRows(rows, pgx.RowToStructByNameLax[ListingCountAtTime]); err != nil { return fmt.Errorf("failed to scan rows: %w", err) } return nil }) return counts, err } type mockShopSchemaInfo struct { platform accounts.Platform shopTable string listingsTable string listingCountsView string } func getMockShopSchemaInfo(platform accounts.Platform) (mockShopSchemaInfo, bool) { for _, in := range getAllMockShopSchemaInfos() { if in.platform == platform { return in, true } } return mockShopSchemaInfo{}, false } func getAllMockShopSchemaInfos() []mockShopSchemaInfo { return []mockShopSchemaInfo{ { platform: accounts.Amazon, shopTable: "mock.shop_amazon", listingsTable: "mock.shop_amazon_listings", listingCountsView: "mock.shop_amazon_listing_counts", }, { platform: accounts.BigCartel, shopTable: "mock.shop_big_cartel", listingsTable: "mock.shop_big_cartel_listings", listingCountsView: "mock.shop_big_cartel_listing_counts", }, { platform: accounts.Ebay, shopTable: "mock.shop_ebay", listingsTable: "mock.shop_ebay_listings", listingCountsView: "mock.shop_ebay_listing_counts", }, { platform: accounts.Ecwid, shopTable: "mock.shop_ecwid", listingsTable: "mock.shop_ecwid_listings", listingCountsView: "mock.shop_ecwid_listing_counts", }, { platform: accounts.Etsy, shopTable: "mock.shop_etsy", listingsTable: "mock.shop_etsy_listings", listingCountsView: "mock.shop_etsy_listing_counts", }, { platform: accounts.Shopify, shopTable: "mock.shop_shopify", listingsTable: "mock.shop_shopify_listings", listingCountsView: "mock.shop_shopify_listing_counts", }, { platform: accounts.SquareOnline, shopTable: "mock.shop_square_online", listingsTable: "mock.shop_square_online_listings", listingCountsView: "mock.shop_square_online_listing_counts", }, { platform: accounts.Squarespace, shopTable: "mock.shop_squarespace", listingsTable: "mock.shop_squarespace_listings", listingCountsView: "mock.shop_squarespace_listing_counts", }, { platform: accounts.Tiktok, shopTable: "mock.shop_tiktok", listingsTable: "mock.shop_tiktok_listings", listingCountsView: "mock.shop_tiktok_listing_counts", }, { platform: accounts.WalmartMarketplace, shopTable: "mock.shop_walmart_marketplace", listingsTable: "mock.shop_walmart_marketplace_listings", listingCountsView: "mock.shop_walmart_marketplace_listing_counts", }, { platform: accounts.Wix, shopTable: "mock.shop_wix", listingsTable: "mock.shop_wix_listings", listingCountsView: "mock.shop_wix_listing_counts", }, { platform: accounts.WooCommerce, shopTable: "mock.shop_woo_commerce", listingsTable: "mock.shop_woo_commerce_listings", listingCountsView: "mock.shop_woo_commerce_listing_counts", }, { platform: accounts.Zoho, shopTable: "mock.shop_zoho", listingsTable: "mock.shop_zoho_listings", listingCountsView: "mock.shop_zoho_listing_counts", }, } }