Add eBay Browse API integration with daily call quota
eBay marketplaces are now polled through eBay's official Buy > Browse API (client-credentials OAuth2) instead of an Apify scraper actor; Apify still handles Yahoo JP and Mercari. Browse API calls are tracked per day in a new ebay_api_usage table and capped (default 5000, configurable) on eBay's Pacific-time reset clock, so polling halts before the limit is hit. Credentials live in config.toml [ebay] and are overridable via /settings, which also surfaces the day's running call count. Also carries the server.secure_cookies config plumbing (field, accessor, example) consumed by the following commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -213,6 +213,60 @@ func (s *Store) SetSetting(ctx context.Context, key, value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ============ ebay api usage ============
|
||||
|
||||
// ebayResetLoc is the timezone eBay's API rate limits reset in: midnight
|
||||
// Pacific (it observes US DST). If the zone database is somehow unavailable
|
||||
// we fall back to UTC rather than failing — main.go embeds time/tzdata so in
|
||||
// practice the lookup always succeeds.
|
||||
var ebayResetLoc = func() *time.Location {
|
||||
loc, err := time.LoadLocation("America/Los_Angeles")
|
||||
if err != nil {
|
||||
return time.UTC
|
||||
}
|
||||
return loc
|
||||
}()
|
||||
|
||||
// ebayUsageDay returns the date key used to bucket eBay API calls, aligned to
|
||||
// eBay's own quota reset (midnight US Pacific).
|
||||
func ebayUsageDay() string {
|
||||
return time.Now().In(ebayResetLoc).Format("2006-01-02")
|
||||
}
|
||||
|
||||
// EbayUsageToday returns the number of eBay Browse API calls recorded for the
|
||||
// current UTC day. A missing row counts as zero.
|
||||
func (s *Store) EbayUsageToday(ctx context.Context) (int, error) {
|
||||
var n int
|
||||
err := s.DB.QueryRowContext(ctx,
|
||||
`SELECT call_count FROM ebay_api_usage WHERE usage_date = ?`, ebayUsageDay()).Scan(&n)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// IncrementEbayUsage records one eBay Browse API call against the current UTC
|
||||
// day and returns the new running total.
|
||||
func (s *Store) IncrementEbayUsage(ctx context.Context) (int, error) {
|
||||
day := ebayUsageDay()
|
||||
_, err := s.DB.ExecContext(ctx, `
|
||||
INSERT INTO ebay_api_usage (usage_date, call_count) VALUES (?, 1)
|
||||
ON CONFLICT(usage_date) DO UPDATE SET call_count = call_count + 1
|
||||
`, day)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var n int
|
||||
if err := s.DB.QueryRowContext(ctx,
|
||||
`SELECT call_count FROM ebay_api_usage WHERE usage_date = ?`, day).Scan(&n); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ============ items ============
|
||||
|
||||
func (s *Store) CreateItem(ctx context.Context, it *models.Item) (int64, error) {
|
||||
|
||||
@@ -90,6 +90,15 @@ INSERT OR IGNORE INTO settings (key, value) VALUES
|
||||
('global_poll_interval_minutes', '60'),
|
||||
('match_confidence_threshold', '0.6');
|
||||
|
||||
-- ebay_api_usage tracks Browse API calls per day so Veola can surface
|
||||
-- consumption and halt polling before the developer keyset's daily call
|
||||
-- limit is exceeded. usage_date is YYYY-MM-DD in US Pacific time, matching
|
||||
-- eBay's own quota reset.
|
||||
CREATE TABLE IF NOT EXISTS ebay_api_usage (
|
||||
usage_date TEXT PRIMARY KEY,
|
||||
call_count INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
data BLOB NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user