mirror of
https://github.com/wahyd4/hey-search.git
synced 2026-08-08 21:05:14 +10:00
Add docker-compose
This commit is contained in:
+77
-45
@@ -3,13 +3,15 @@
|
|||||||
## Search
|
## Search
|
||||||
|
|
||||||
- **Web search** — aggregates results from Brave, DuckDuckGo, Google, and Bing concurrently
|
- **Web search** — aggregates results from Brave, DuckDuckGo, Google, and Bing concurrently
|
||||||
- **Image search** — Pinterest-style masonry layout respecting natural image aspect ratios, with lightbox viewer and keyboard navigation (←/→ to browse, Escape to close)
|
- **Image search** — masonry grid respecting natural aspect ratios, with lightbox viewer and keyboard navigation (←/→ to browse, Escape to close)
|
||||||
- **Image size filter** — filter images by size (Large, Medium, Small) — applied server-side across all engines
|
- **Image size filter** — filter images by size (Large, Medium, Small) applied server-side across all engines
|
||||||
- **Autocomplete** — live search suggestions as you type (cascades Google → DuckDuckGo → Brave)
|
- **Sort order** — sort results by engine rank (default), newest first, or oldest first
|
||||||
|
- **Autocomplete** — live search suggestions as you type (200ms debounce, ↑↓/Enter/Escape keyboard support)
|
||||||
- **Pagination** — navigate through result pages; URL reflects current state (`?q=...&page=2`)
|
- **Pagination** — navigate through result pages; URL reflects current state (`?q=...&page=2`)
|
||||||
- **URL deduplication** — duplicate results from multiple engines are merged automatically
|
- **URL deduplication** — duplicate results from multiple engines are merged automatically
|
||||||
- **Domain exclusion** — exclude specific websites from search results; settings persist in SQLite
|
- **Domain exclusion** — exclude specific websites from all search results; settings persist in SQLite
|
||||||
- **Search stats** — per-engine result counts, status indicators (ok/error/timeout), collapsible sidebar
|
- **Engine filter** — restrict a search to specific engines via the `engines` query parameter (e.g. `engines=google,bing`)
|
||||||
|
- **Per-engine stats** — result counts, status (ok / error / timeout), and error messages per engine shown in a collapsible bar
|
||||||
|
|
||||||
## Search Engines
|
## Search Engines
|
||||||
|
|
||||||
@@ -18,80 +20,110 @@
|
|||||||
- **DuckDuckGo** — HTML Lite scraping for web, API for images
|
- **DuckDuckGo** — HTML Lite scraping for web, API for images
|
||||||
- **Brave** — Brave Search API for web and images
|
- **Brave** — Brave Search API for web and images
|
||||||
|
|
||||||
## REST API
|
|
||||||
|
|
||||||
- Full REST API for search, autocomplete, and engine management
|
|
||||||
- `/api/search` accepts both **GET and POST** requests with query string parameters
|
|
||||||
- Compatibility parameters on `/api/search`: `pageNumber` (alias for `page`), `numResults`, `format`, `imageProxy`, `safesearch`
|
|
||||||
- OpenAPI specification with interactive docs via [Swagger UI](https://swagger.io/tools/swagger-ui/) (`/docs`) and [Redoc](https://github.com/Redocly/redoc) (`/redoc`)
|
|
||||||
|
|
||||||
## Engine Management
|
## Engine Management
|
||||||
|
|
||||||
- **4 built-in engines**: Brave, DuckDuckGo, Google, Bing
|
- **4 built-in engines**: Brave, DuckDuckGo, Google, Bing
|
||||||
- Enable or disable engines via the UI or API at runtime
|
- Enable or disable engines via the UI or API at runtime; settings persist in SQLite
|
||||||
|
- **Drag-and-drop reordering** — set engine priority order; affects result ordering and display badges
|
||||||
- Each engine supports both web and image search categories
|
- Each engine supports both web and image search categories
|
||||||
- Google and Bing include automatic fallback mechanisms for resilience
|
- Google and Bing include automatic fallback mechanisms for resilience
|
||||||
|
|
||||||
## Reliability
|
## Search History
|
||||||
|
|
||||||
- **Retry mechanism** — failed upstream requests are retried with exponential backoff (via [tenacity](https://github.com/jd/tenacity))
|
- **Automatic recording** — every search query is saved with timestamp and category
|
||||||
- **Error reporting** — when an upstream engine fails or times out, the UI shows a toast notification identifying which engine had issues
|
- **History page** (`/history`) — paginated, date-grouped list of recent searches (Today, Yesterday, etc.)
|
||||||
- **Graceful degradation** — partial failures don't block results from other engines
|
- **Re-run any query** — click a history entry to instantly re-search it
|
||||||
|
- **Delete entries** — remove individual entries or clear all history at once
|
||||||
|
|
||||||
|
## Analytics Dashboard
|
||||||
|
|
||||||
|
- **Stats page** (`/stats`) — full analytics dashboard with configurable time range (7 / 30 / 90 days)
|
||||||
|
- **KPI cards** — total searches, total clicks, click-through rate, average clicked result position
|
||||||
|
- **Daily trend chart** — bar chart of search volume per day
|
||||||
|
- **Top queries** — ranked list of most-searched terms with counts
|
||||||
|
- **Clicks by engine** — bar chart showing which engines' results get clicked most
|
||||||
|
- **Click position distribution** — histogram of which result ranks users click on
|
||||||
|
- **Top clicked results** — ranked list of most-clicked URLs with title, engine, and count
|
||||||
|
- **Click tracking** — every result click is recorded server-side (fire-and-forget from the frontend)
|
||||||
|
|
||||||
## Caching
|
## Caching
|
||||||
|
|
||||||
- **Redis cache** — identical searches served from Redis cache to reduce upstream load and latency
|
- **Redis cache** — identical searches served from Redis to reduce upstream load and latency
|
||||||
- **Configurable TTL** — cache duration adjustable from 0 (disabled) to 168 hours (1 week); default 6 hours
|
- **Configurable TTL** — cache duration adjustable from 0 (disabled) to 168 hours (1 week); default 6 hours
|
||||||
- **UI controls** — preset buttons + slider in Settings → Cache tab, with flush button
|
- **Cache status** — search response includes a `cached` flag; the stats bar indicates cache hits
|
||||||
|
- **UI controls** — TTL slider + preset buttons, Redis URL input, and flush button in Settings → Cache tab
|
||||||
- **Optional** — when `REDIS_URL` is not set or Redis is unreachable, caching is silently disabled
|
- **Optional** — when `REDIS_URL` is not set or Redis is unreachable, caching is silently disabled
|
||||||
- **Deterministic keys** — cache key derived from query + category + page + image_size + engines
|
- **Deterministic keys** — cache key derived from query + category + page + image_size + engines
|
||||||
|
|
||||||
## Bookmarks
|
## Bookmarks
|
||||||
|
|
||||||
- **Bookmark any result** — click the bookmark icon on web or image search results to save them
|
- **Bookmark any result** — click the bookmark icon on web or image results to save them
|
||||||
- **Bookmarks page** — dedicated `/bookmarks` page to browse all saved items
|
- **Bookmarks page** (`/bookmarks`) — dedicated page to browse all saved items
|
||||||
- **Filter by type** — tabs to filter All / Web / Images
|
- **Filter by type** — tabs to filter All / Web / Images
|
||||||
- **Masonry image grid** — saved images displayed in a masonry layout
|
- **Masonry image grid** — saved images displayed in a masonry layout with hover overlays
|
||||||
- **Remove bookmarks** — delete individual bookmarks from the bookmarks page
|
- **Remove bookmarks** — delete individual bookmarks from the bookmarks page
|
||||||
- **Persistent storage** — bookmarks stored in SQLite alongside other app data
|
- **Persistent storage** — bookmarks stored in SQLite alongside other app data
|
||||||
|
|
||||||
|
## Background Images
|
||||||
|
|
||||||
|
- **Homepage backgrounds** — random background image displayed on the home page (sourced from Unsplash / Picsum)
|
||||||
|
- **Local caching** — images are downloaded and stored in `data/backgrounds/` to avoid re-fetching
|
||||||
|
- **Auto-refresh** — configurable refresh interval (1–1440 minutes, default 30) fetches new images automatically
|
||||||
|
- **Background gallery** (`/backgrounds`) — browse and manage all downloaded backgrounds; click to preview, view file size and source
|
||||||
|
- **Manual refresh** — refresh button in the gallery to fetch a new image immediately
|
||||||
|
- **Enable / disable** — toggle backgrounds on or off in Settings → Background tab
|
||||||
|
- **iOS color sampling** — samples edge pixels from the background to set the `theme-color` meta tag, preventing white/black bars in Safari
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
- **Unified settings modal** — single ⚙ button opens a tabbed modal with four tabs:
|
||||||
|
1. **Engines** — enable/disable and drag-to-reorder search engines
|
||||||
|
2. **Excluded Sites** — add or remove domains from the search blocklist
|
||||||
|
3. **Cache** — view Redis status, set TTL, update Redis URL, flush cache
|
||||||
|
4. **Background** — enable/disable background images, set refresh interval
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
- Full REST API for search, autocomplete, engine management, bookmarks, history, stats, and settings
|
||||||
|
- `/api/search` accepts both **GET and POST** requests with query string parameters
|
||||||
|
- Compatibility parameters: `pageNumber` (alias for `page`), `numResults`, `format`, `imageProxy`, `safesearch`
|
||||||
|
- Every result includes `result_id`, `rank`, `engine`, and `published_date`
|
||||||
|
- `has_next` and `total_results` fields for cursor-aware pagination
|
||||||
|
- `X-Response-Time-Ms` response header on all endpoints
|
||||||
|
- OpenAPI specification with interactive docs via [Swagger UI](https://swagger.io/tools/swagger-ui/) (`/docs`) and [Redoc](https://github.com/Redocly/redoc) (`/redoc`)
|
||||||
|
|
||||||
|
## Reliability
|
||||||
|
|
||||||
|
- **Retry mechanism** — failed upstream requests are retried with exponential backoff (via [tenacity](https://github.com/jd/tenacity), 2 attempts)
|
||||||
|
- **Error reporting** — when an upstream engine fails or times out, the UI shows a toast notification identifying which engine had issues
|
||||||
|
- **Graceful degradation** — partial failures don't block results from other engines
|
||||||
|
|
||||||
## UI / UX
|
## UI / UX
|
||||||
|
|
||||||
- **Mobile-first** responsive design built with React, Tailwind CSS, and shadcn theming
|
- **Mobile-first** responsive design built with React, Tailwind CSS, and shadcn theming
|
||||||
- Clean search home page with branded gradient header
|
- Web results show favicons, engine badges, publish dates, and content snippets
|
||||||
- Web results show favicons, engine badges, and content snippets
|
- Image results in a masonry grid with hover previews and a full-screen lightbox
|
||||||
- Image results displayed in a masonry grid with hover previews and a full lightbox with arrow-key navigation
|
- **Visited link colours** — clicked links turn purple to distinguish from unvisited results
|
||||||
- **Visited link colors** — clicked links turn purple to distinguish from unvisited results
|
- **Dropdown navigation menu** — consistent ☰ menu across all pages (search, bookmarks, history, stats, gallery)
|
||||||
- Engine settings modal with toggle switches
|
|
||||||
- Excluded domains management modal with add/remove UI
|
|
||||||
- **Unified settings modal** — single Settings button opens a tabbed modal (Engines, Excluded Sites)
|
|
||||||
- Keyboard navigation for autocomplete suggestions (↑↓ arrows, Enter, Escape)
|
- Keyboard navigation for autocomplete suggestions (↑↓ arrows, Enter, Escape)
|
||||||
- Footer with link to interactive API documentation (Swagger UI)
|
- URL-based routing with browser history support — shareable search URLs
|
||||||
|
|
||||||
## Accessibility
|
## Accessibility
|
||||||
|
|
||||||
- **Skip to main content** link for keyboard users
|
- **Skip to main content** link for keyboard users
|
||||||
- Image cards are real `<a>` links (Cmd/Ctrl-click, right-click, open-in-new-tab)
|
- Image cards are real `<a>` links (Cmd/Ctrl-click, right-click, open-in-new-tab all work)
|
||||||
- `aria-label` on all icon-only buttons and interactive elements
|
- `aria-label` on all icon-only buttons and interactive elements
|
||||||
- Search input with `<label>`, `name`, `type="search"`, and ARIA combobox pattern
|
- Search input with `<label>`, `name`, `type="search"`, and ARIA combobox pattern
|
||||||
- Visible `focus-visible` ring on all focusable elements
|
- Visible `focus-visible` ring on all focusable elements
|
||||||
- `aria-live` regions for loading/results/error announcements
|
- `aria-live` regions for loading / results / error announcements
|
||||||
- Proper heading hierarchy (`h1`/`h2`) and landmark elements (`<nav>`, `<main>`, `<aside>`)
|
- Proper heading hierarchy (`h1`/`h2`) and landmark elements (`<nav>`, `<main>`, `<aside>`)
|
||||||
- Dialog semantics on modals and lightbox (`role="dialog"`, `aria-modal`)
|
- Dialog semantics on modals and lightbox (`role="dialog"`, `aria-modal`)
|
||||||
|
|
||||||
## REST API
|
|
||||||
|
|
||||||
- Full REST API for search, autocomplete, and engine management
|
|
||||||
- `/api/search` accepts both **GET and POST** requests with query string parameters
|
|
||||||
- Compatibility parameters on `/api/search`: `pageNumber` (alias for `page`), `numResults`, `format`, `imageProxy`, `safesearch`
|
|
||||||
- Every result includes `result_id`, `rank`, `engine`, and `timestamp` for agent integration
|
|
||||||
- `has_next` and `total_results` fields for cursor-aware pagination
|
|
||||||
- Standardized error schema (`code`/`message`/`details`/`retry_hint`) on all error responses
|
|
||||||
- `X-Response-Time-Ms` and rate-limit headers on all responses
|
|
||||||
- Copy-paste `curl` examples in OpenAPI docs for every endpoint
|
|
||||||
- OpenAPI specification with interactive docs via [Swagger UI](https://swagger.io/tools/swagger-ui/) (`/docs`) and [Redoc](https://github.com/Redocly/redoc) (`/redoc`)
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
- **Docker** — multi-stage Dockerfile: Node.js frontend build → Python production image
|
- **Docker** — multi-stage Dockerfile: Node.js frontend build → Python production image
|
||||||
- Backend uses [uv](https://docs.astral.sh/uv/) for fast, reproducible dependency management
|
- **GHCR image** — pre-built image published to GitHub Container Registry on every release
|
||||||
|
- **Data volume** — `/app/data` stores SQLite databases and background images; mount for persistence
|
||||||
|
- **Environment variables** — `DATA_DIR` (data directory path), `REDIS_URL` (optional Redis connection string)
|
||||||
|
- Backend uses [uv](https://docs.astral.sh/uv/) for fast, reproducible Python dependency management
|
||||||
|
|
||||||
|
|||||||
@@ -53,19 +53,28 @@ just dev
|
|||||||
REDIS_URL=redis://192.168.1.2:6399 just dev
|
REDIS_URL=redis://192.168.1.2:6399 just dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Docker
|
### 2. Docker Compose
|
||||||
|
|
||||||
|
The easiest way to run HeySearch with Redis caching in one command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open [http://localhost:8000](http://localhost:8000). Data and Redis are persisted in named volumes automatically.
|
||||||
|
|
||||||
|
### 3. Docker (standalone)
|
||||||
|
|
||||||
|
```bash
|
||||||
# Basic — data stored in anonymous volume
|
# Basic — data stored in anonymous volume
|
||||||
docker run -p 8000:8000 ghcr.io/wahyd4/hey-search
|
docker run -p 8000:8000 ghcr.io/wahyd4/hey-search
|
||||||
|
|
||||||
# Recommended — mount data directory for persistence
|
# Recommended — mount data directory for persistence
|
||||||
docker run -p 8000:8000 -v ./hey-search-data:/app/data hey-search
|
docker run -p 8000:8000 -v ./hey-search-data:/app/data ghcr.io/wahyd4/hey-search
|
||||||
|
|
||||||
# With Redis
|
# With Redis
|
||||||
docker run -p 8000:8000 -v ./hey-search-data:/app/data \
|
docker run -p 8000:8000 -v ./hey-search-data:/app/data \
|
||||||
-e REDIS_URL=redis://your-redis:6379 hey-search
|
-e REDIS_URL=redis://your-redis:6379 ghcr.io/wahyd4/hey-search
|
||||||
```
|
```
|
||||||
|
|
||||||
The `/app/data` volume stores the SQLite database (engine settings, excluded domains, cache config). Mount it to preserve your settings across container restarts.
|
The `/app/data` volume stores the SQLite database (engine settings, excluded domains, cache config). Mount it to preserve your settings across container restarts.
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ghcr.io/wahyd4/hey-search:latest
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
- DATA_DIR=/app/data
|
||||||
|
volumes:
|
||||||
|
- hey-search-data:/app/data
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
volumes:
|
||||||
|
- hey-search-redis:/data
|
||||||
|
command: redis-server --save 60 1 --loglevel warning
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
hey-search-data:
|
||||||
|
hey-search-redis:
|
||||||
Reference in New Issue
Block a user