feat: add Stalker results page with filtering and pagination
- Introduced StalkerResultItem and StalkerResultsResponse types for handling API responses. - Implemented StalkerExplorer component for displaying Stalker results with search and filter options. - Added sorting functionality for Stalker results table. - Enhanced Dashboard to include a button for navigating to Stalker results. - Updated routing to support Stalker results page. - Improved styles for section headers and inventory columns in the results table.
This commit is contained in:
122
src/database.ts
122
src/database.ts
@@ -333,4 +333,126 @@ export function initDb(dbPath: string): void {
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_product_results_fetched_at ON product_analysis_results(fetched_at DESC);`,
|
||||
);
|
||||
initStalkerDb(database);
|
||||
}
|
||||
|
||||
export function initStalkerDb(database: Database): void {
|
||||
resetLegacyStalkerSchema(database);
|
||||
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS stalker_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
input_file TEXT NOT NULL,
|
||||
started_at TEXT NOT NULL,
|
||||
completed_at TEXT,
|
||||
requested_asins INTEGER NOT NULL DEFAULT 0,
|
||||
skipped_asins INTEGER NOT NULL DEFAULT 0,
|
||||
scanned_asins INTEGER NOT NULL DEFAULT 0,
|
||||
source_asins_with_matches INTEGER NOT NULL DEFAULT 0,
|
||||
candidate_sellers INTEGER NOT NULL DEFAULT 0,
|
||||
qualifying_sellers INTEGER NOT NULL DEFAULT 0,
|
||||
matched_sellers INTEGER NOT NULL DEFAULT 0,
|
||||
seller_metadata_requests INTEGER NOT NULL DEFAULT 0,
|
||||
seller_storefront_requests INTEGER NOT NULL DEFAULT 0,
|
||||
persisted_inventory_asins INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL,
|
||||
error_message TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS stalker_asin_scans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
run_id INTEGER NOT NULL,
|
||||
source_asin TEXT NOT NULL,
|
||||
title TEXT,
|
||||
offer_count INTEGER NOT NULL DEFAULT 0,
|
||||
candidate_seller_count INTEGER NOT NULL DEFAULT 0,
|
||||
matched_seller_count INTEGER NOT NULL DEFAULT 0,
|
||||
fetched_at TEXT NOT NULL,
|
||||
raw_product_json TEXT,
|
||||
UNIQUE(run_id, source_asin),
|
||||
FOREIGN KEY (run_id) REFERENCES stalker_runs(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS stalker_sellers (
|
||||
seller_id TEXT PRIMARY KEY,
|
||||
seller_name TEXT,
|
||||
rating REAL,
|
||||
rating_count INTEGER,
|
||||
storefront_asin_total INTEGER,
|
||||
persisted_inventory_sample_count INTEGER,
|
||||
last_updated_at TEXT NOT NULL,
|
||||
raw_seller_json TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS stalker_asin_sellers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
scan_id INTEGER NOT NULL,
|
||||
seller_id TEXT NOT NULL,
|
||||
offer_price REAL,
|
||||
condition TEXT,
|
||||
is_fba INTEGER,
|
||||
stock INTEGER,
|
||||
seller_rating REAL,
|
||||
seller_rating_count INTEGER,
|
||||
raw_offer_json TEXT,
|
||||
UNIQUE(scan_id, seller_id),
|
||||
FOREIGN KEY (scan_id) REFERENCES stalker_asin_scans(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (seller_id) REFERENCES stalker_sellers(seller_id)
|
||||
);
|
||||
`);
|
||||
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS stalker_seller_inventory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
run_id INTEGER NOT NULL,
|
||||
seller_id TEXT NOT NULL,
|
||||
asin TEXT NOT NULL,
|
||||
last_seen_at TEXT NOT NULL,
|
||||
raw_inventory_json TEXT,
|
||||
UNIQUE(run_id, seller_id, asin),
|
||||
FOREIGN KEY (run_id) REFERENCES stalker_runs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (seller_id) REFERENCES stalker_sellers(seller_id)
|
||||
);
|
||||
`);
|
||||
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_runs_started_at ON stalker_runs(started_at DESC);`,
|
||||
);
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_scans_run_id ON stalker_asin_scans(run_id);`,
|
||||
);
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_scans_source_asin ON stalker_asin_scans(source_asin);`,
|
||||
);
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_asin_sellers_seller_id ON stalker_asin_sellers(seller_id);`,
|
||||
);
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_inventory_seller_id ON stalker_seller_inventory(seller_id);`,
|
||||
);
|
||||
database.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_stalker_inventory_asin ON stalker_seller_inventory(asin);`,
|
||||
);
|
||||
}
|
||||
|
||||
function resetLegacyStalkerSchema(database: Database): void {
|
||||
const runColumns = database
|
||||
.query("PRAGMA table_info(stalker_runs)")
|
||||
.all() as Array<{ name: string }>;
|
||||
if (runColumns.length === 0) return;
|
||||
|
||||
const columnNames = new Set(runColumns.map((column) => column.name));
|
||||
if (columnNames.has("scanned_asins")) return;
|
||||
|
||||
database.run("DROP TABLE IF EXISTS stalker_seller_inventory");
|
||||
database.run("DROP TABLE IF EXISTS stalker_asin_sellers");
|
||||
database.run("DROP TABLE IF EXISTS stalker_sellers");
|
||||
database.run("DROP TABLE IF EXISTS stalker_asin_scans");
|
||||
database.run("DROP TABLE IF EXISTS stalker_runs");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user