Commit Graph

46 Commits

Author SHA1 Message Date
mariosemes
a6a398d57e Add BigBang.hr store config (stealth Puppeteer, Cloudflare bypass)
Croatian electronics retailer behind Cloudflare. Works via Docker
Chromium with stealth plugin. 35 products per page, article.cp
containers with .cp-title for names, .current-price for prices.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:39:43 +01:00
mariosemes
4a67539a07 Add stealth mode to browser scraper and Ronis.hr store
- Switch puppeteer to puppeteer-extra with stealth plugin to
  bypass Cloudflare bot detection
- Add Ronis.hr store config (JS-rendered, 48 products per page)
- Stealth mode patches navigator.webdriver, chrome runtime, and
  other fingerprints that Cloudflare checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:36:19 +01:00
mariosemes
35577bc3c7 Add ADM.hr store config (cheerio, 24 products per page)
Croatian electronics retailer. Uses .product-grid.product-item
containers, h2.title for names, .price for prices. Static HTML,
no JS rendering needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:28:46 +01:00
mariosemes
4a1fc874c1 Security and code quality audit fixes
Security:
- Fix SQL injection in updateStore — whitelist allowed field names
- Restrict CORS to same-origin in production
- Cap results at 200 per store to prevent memory issues

Code quality:
- Extract shared queryAll/queryOne to src/server/db/query.ts
- Remove duplicated DB helpers from 5 files
- Handle render_js boolean-to-integer conversion in updateStore

UX:
- Validate headers_json as valid JSON before saving (both forms)
- Show error message if JSON is invalid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:24:56 +01:00
mariosemes
4463ef594d Apply 1400px max width to all admin pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:16:50 +01:00
mariosemes
1f0f1c1a0a Cap stores page width at 1400px to match results page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:15:42 +01:00
mariosemes
72980e0dd6 Flag broken stores on search page with red X and auto-deselect
- Add last_scrape_ok and last_scrape_at columns (migration 004)
- Update scrape status after every search, test, and streaming search
- Search page: broken stores show red X checkbox, strikethrough name,
  "failing" label, and are auto-deselected on page load
- Untested stores show "untested" label
- Users can still manually select broken stores if they want to try

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:12:44 +01:00
mariosemes
cb71421d8d Add per-store test_query for automated store testing
Each store can now have its own test_query (e.g., "logitech" for
electronics stores). The "Test All" button uses each store's
configured query instead of prompting — just click and watch.

- Add test_query column (migration 003)
- Add field to YAML sync, store forms, and route schema
- Set test_query in HG Spot and Links.hr configs
- Test All runs immediately using per-store queries
- Hover test result to see which query was used

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:08:41 +01:00
mariosemes
b3647be434 Add Test All Stores button with live per-store results
Click 'Test All' to enter a search query, then each enabled store
is tested sequentially. A 'Test Result' column appears showing:
- Spinner while testing
- Green checkmark with product count and duration on success
- Red X with error on failure
- Disabled stores show dash

Tests run one at a time so results stream in visibly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:04:22 +01:00
mariosemes
4ea48b3303 Layout category store lists as horizontal columns
Each category is now a column side by side, so adding more
categories grows horizontally instead of vertically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:01:55 +01:00
mariosemes
fb21024818 Make category name larger and left-align stores with category
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:01:09 +01:00
mariosemes
942d252663 Replace store badge chips with vertical checkbox list
Clean vertical layout with category headers and indented store
checkboxes. Filled purple checkbox when selected, empty border
when not. Hover highlight on each row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:00:06 +01:00
mariosemes
d9a3693469 Add category-grouped store picker on search page
Replaces dropdown filters with visual store picker organized by
category. Each category shows as a header with checkbox — clicking
it selects/deselects all stores in that category. Individual stores
are toggleable chips with checkmarks. Partial selection shows a
dash indicator on the category checkbox. Only passes specific
store IDs to search when not all stores are selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:58:45 +01:00
mariosemes
631e07f7ae Fix thumbnail preview z-index by using fixed positioning
Preview now uses position:fixed with z-index 9999, positioned
dynamically via JS on mouseenter. No longer clipped by parent
overflow containers or table cells.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:57:18 +01:00
mariosemes
f47e019427 Make thumbnail preview height hug the image instead of fixed square
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:55:45 +01:00
mariosemes
d0081d347a Enlarge thumbnail preview to 320x320 with even padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:54:32 +01:00
mariosemes
61dc793edb Enlarge thumbnail hover preview to 256x256
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:53:47 +01:00
mariosemes
84a250e955 Add thumbnail hover preview on results table
Hovering a product thumbnail shows a 192x192 enlarged preview
in a dark container with border and shadow. Appears to the right
of the thumbnail with a smooth scale+fade animation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:53:22 +01:00
mariosemes
4ec673b128 Prevent Store and Link columns from wrapping to two lines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:52:03 +01:00
mariosemes
59aaf0c8a5 Drop tsx watch mode — use plain tsx for dev server
tsx watch hangs when puppeteer/chromium imports are involved.
Plain tsx starts reliably. Hot reload isn't critical since
server changes are infrequent compared to client changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:49:42 +01:00
mariosemes
a3ae3b248f Use remote Chromium container instead of local Puppeteer launch
- Add browserless/chromium container to docker-compose
- Add docker-compose.dev.yml for local dev (Chromium on port 3001)
- Browser scraper connects via WebSocket (CHROMIUM_WS env var)
- Falls back to local launch if CHROMIUM_WS not set
- Remove Chromium install from Dockerfile (smaller image)
- Auto-reconnect on browser disconnect

Tested: remote Chromium connects in ~500ms, HG Spot scrapes in
~2.2s total. No longer blocks the Node.js event loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:47:40 +01:00
mariosemes
0e2e8d1766 Pre-launch Chromium on server startup to avoid cold-start blocking
Chromium cold launch takes several seconds and blocks the event
loop, preventing SSE events from flushing. Now the browser is
warmed up during server startup if any store uses render_js,
so the first search doesn't pay the launch penalty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:42:15 +01:00
mariosemes
80335d213c Add step-by-step logging to browser scraper and skip HTML capture
Logs each phase (launch, navigate, wait selector, extract, close)
so we can diagnose where Puppeteer gets stuck. Also skips the
expensive page.content() call since full HTML is only needed
for the test endpoint, not search.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:40:23 +01:00
mariosemes
2c8ae5f628 Switch streaming search to sequential queue instead of parallel
Parallel scraping with Puppeteer blocks the Node.js event loop,
preventing SSE events from flushing. Sequential processing means
each store completes, sends its event, and the client sees it
before the next store starts.

Also sorts stores so cheerio-based (fast) stores run first,
giving the user results sooner while Puppeteer stores load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:37:27 +01:00
mariosemes
0e6ec21e81 Add event loop yields so SSE events flush during search
Without yields, the async operations block the event loop and
SSE events pile up unsent until the entire search completes.
Adding setTimeout(0) yields after start and store_complete events
lets Node.js flush the write buffer to the client.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:28:35 +01:00
mariosemes
75b8759805 Fix Puppeteer hanging by using domcontentloaded instead of networkidle2
networkidle2 waits for all network activity to settle, which hangs
on sites with analytics, trackers, and websockets. domcontentloaded
fires much earlier, then waitForSelector handles the dynamic content.
HG Spot now completes in ~2.5s instead of timing out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:26:16 +01:00
mariosemes
b243e06175 Fix SSE streaming by hijacking Fastify response
Fastify was buffering the SSE response, causing store progress
to appear stuck. Using reply.hijack() hands off the raw response
so events flush immediately to the client.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:22:45 +01:00
mariosemes
37425812e0 Add real-time per-store search progress via SSE streaming
Backend: new /api/search/stream SSE endpoint that emits events
as each store completes: start (store list), store_complete
(results + duration), store_error, and done (final meta).

Frontend: results page now shows live progress per store with
spinning indicators while searching, checkmarks when done, and
X marks on errors. Each store chip shows product count and
response time. Results stream into the table as stores complete
instead of waiting for all stores to finish.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:15:50 +01:00
mariosemes
fe56c3b17e Lazy-load puppeteer to fix tsx watch hanging on startup
Puppeteer import at top level was blocking tsx watch mode,
preventing the server from starting. Now imported dynamically
only when a JS-rendered store is actually scraped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:08:09 +01:00
mariosemes
42f2cab158 Add Links.hr store config (cheerio, no JS rendering needed)
Croatian electronics retailer, 24 products per page.
Uses .card.mobile-card containers with h3 for names,
.product-price for prices, a.card-link for links.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:56:36 +01:00
mariosemes
0693b66b3c Make filter input larger and more prominent
Bigger padding, larger icon, rounded-lg, ring on focus,
text-sm instead of text-xs. Matches the search bar feel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:53:10 +01:00
mariosemes
acda8d5270 Replace search bar with title and New Search button on results page
Header now shows "Search results for: <keyword>" with meta stats,
and a "New Search" button that navigates back to the homepage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:52:22 +01:00
mariosemes
ac05e83bdd Move store chips to own row below filter input
Filter bar now has two rows: text filter + count on top,
store toggle chips on the bottom. Chips wrap on narrow screens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:50:54 +01:00
mariosemes
4fb0d65710 Replace store dropdown with checkbox toggle chips
Each store appears as a clickable chip with a checkmark. Click to
exclude a store from results (greys out with strikethrough). Click
again to re-include. Multiple stores can be toggled independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:49:55 +01:00
mariosemes
a8d4a9ce3a Always show store filter dropdown on results table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:47:43 +01:00
mariosemes
68b3f9ecbc Make search and filter inputs full width within container
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:46:07 +01:00
mariosemes
23d46384d7 Cap results page width at 1400px for readability on wide screens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:44:36 +01:00
mariosemes
9bdd5c4910 Replace results cards with sortable, filterable table
- Compact table view with thumbnail, product name, price, store, link
- Click column headers to sort (toggles asc/desc)
- Filter bar to search within results by product name
- Store dropdown filter when multiple stores present
- Sticky header, hover-reveal "Open" link, sort direction arrows
- Shows "X of Y shown" count when filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:43:32 +01:00
mariosemes
c24b06215b Fix loading state stuck when API calls fail on startup
Add try/catch to all onMount API calls so the UI recovers
gracefully when the server isn't ready yet instead of hanging
on the loading skeleton forever.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:41:17 +01:00
mariosemes
6e740d3db4 Fix CSS @import order — must precede @tailwind directives
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:37:20 +01:00
mariosemes
130ab30fcc Add Puppeteer browser scraping and HG Spot store config
- Add browser-scraper.ts using Puppeteer for JS-heavy stores
- Add render_js flag to store model, migration, YAML sync, and UI
- Scraper engine auto-selects cheerio vs Puppeteer based on flag
- Store forms include JS rendering toggle in Advanced section
- Create first store config: HG Spot (Croatian electronics retailer)
- Update Dockerfile with Chromium for production Puppeteer support

Tested: HG Spot returns 15 products per page with correct names,
prices (EUR), links, and images using headless browser rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:36:20 +01:00
mariosemes
97fb8d9663 Make sidebar collapsible with hamburger toggle
Sidebar is now hidden by default, opened via a small hamburger
button in the top-left corner. Clicking a nav link or the overlay
closes it. Keeps focus on the search bar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:18:23 +01:00
mariosemes
98e326266f Restyle entire frontend with Linear-inspired dark design
- Dark-first design with #0a0a0b backgrounds and subtle borders
- Purple/violet accent colors for primary actions
- Inter font with custom design tokens
- Sidebar navigation replacing top nav bar
- Compact, information-dense tables and cards
- Consistent component classes (btn-primary, input-field, card, label)
- Custom scrollbar and selection styling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:14:14 +01:00
mariosemes
26467a6368 Add YAML-based store configs with bidirectional sync
Stores can now be defined as YAML files in the stores/ directory.
On startup, YAML files are synced into the database. Changes made
via the admin UI are written back to YAML files automatically.

- Add store-sync service (load from files, export to files, write-back)
- Add /api/stores/sync and /api/stores/export endpoints
- Add Sync/Export buttons to admin UI
- Mount stores/ volume in Docker
- Include example store config template

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:06:29 +01:00
mariosemes
8ce5ba62dc Add README with project overview and quick start guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:57:24 +01:00
mariosemes
e0f67d0835 Initial commit: Price Hunter — self-hosted price comparison engine
Complete application scaffolding with:
- Backend: Node.js + Fastify + sql.js (SQLite)
- Frontend: SvelteKit + Tailwind CSS
- Scraper engine with parallel fan-out, rate limiting, cheerio-based parsing
- Store management with CSS selector config and per-store test pages
- Docker setup for single-command deployment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:52 +01:00