A multi-tenant website search-as-a-service backend. Crawl websites, import feeds, and provide full-text search and autocomplete over indexed content.
Production: https://search.loxal.net
- Kotlin 2.3 on JVM 25 (BellSoft Liberica)
- Spring Boot 4 (WebFlux / Reactor Netty, HTTP/2, virtual threads)
- Elasticsearch — all persistence (no ORM, raw HTTP client)
- crawler4j — multi-threaded web crawler with BerkeleyDB JE frontier
- Jsoup / Apache Tika — HTML and PDF content extraction
- Rome — RSS/Atom feed parsing
- SpringDoc OpenAPI — Swagger UI at
/swagger-ui.html
Requires Gradle 9.3.1 (no wrapper — use a system install, e.g. nix run nixpkgs#gradle).
# Build (from the lox/ root)
just build-page-finder
# Or directly
gradle --project-dir page-finder-service build
# Required environment variables
export SERVICE_SECRET="<uuid>"
export ADMIN_SITE_SECRET="<uuid>"
export ELASTICSEARCH_SERVICE="http://localhost:9200"
export EMAIL_SMTP_SECRET="<gmail-app-password>"
# Run
java --enable-native-access=ALL-UNNAMED -XX:+UseZGC \
-jar build/libs/page-finder-service.jar \
--spring.config.additional-location=config/local.yaml
# Tests
gradle --project-dir page-finder-service test
# JMH benchmarks
gradle --project-dir page-finder-service benchmarkThe service listens on port 8001 (HTTP) and 7443 (HTTPS).
./release.shBuilds a linux/amd64 image and pushes to docker.loxal.net/loxal/page-finder:latest.
Interactive API docs are available at /swagger-ui.html and /v3/api-docs when the service is running.
| Method | Path | Description |
|---|---|---|
POST |
/sites |
Create a new site (tenant) |
GET |
/sites/{siteId} |
List page IDs for a site |
DELETE |
/sites/{siteId} |
Clear all pages for a site |
GET |
/sites/{siteId}/profile |
Fetch site profile |
PUT |
/sites/{siteId}/profile |
Update site profile |
| Method | Path | Description |
|---|---|---|
PUT |
/sites/{siteId}/pages |
Index a page |
GET |
/sites/{siteId}/pages?url= |
Fetch a page by URL |
DELETE |
/sites/{siteId}/pages/{pageId} |
Delete a page |
| Method | Path | Description |
|---|---|---|
GET |
/sites/{siteId}/search?query= |
Full-text fuzzy search |
GET |
/sites/{siteId}/autocomplete?query= |
Search-as-you-type suggestions |
| Method | Path | Description |
|---|---|---|
POST |
/sites/{siteId}/crawl |
Trigger a crawl |
POST |
/sites/{siteId}/recrawl |
Re-crawl using stored configs |
| Method | Path | Description |
|---|---|---|
PUT |
/sites/{siteId}/xml |
Import generic XML feed |
PUT |
/sites/{siteId}/rss |
Import RSS/Atom feed |
POST |
/sites/rss |
Import RSS feed into a new site |
- Multi-tenancy: each site gets a
siteId(UUID) +siteSecretfor authentication. Page IDs are SHA-256 ofsiteId + url. - Elasticsearch indexes:
site-page(documents),site-profile(tenant config),svc-singletons(crawl schedules). - Crawler: respects
robots.txt, supports sitemap-only mode, CSS-selector scoped body extraction, and PDF text extraction via Tika. - Standalone Gradle project (formerly part of the
loxGradle monorepo). Sibling:hutils-service.