1
0
Fork
You've already forked bookpile
0
forked from bitboxer/bookpile
This project tries to create a page that you can link to so that your readers have an easy way to buy the book you are talking about on social media or in your blog.
  • HTML 66.8%
  • Elixir 24.9%
  • CSS 5.8%
  • JavaScript 1.9%
  • Dockerfile 0.4%
  • Other 0.1%
2026年05月27日 05:56:21 +02:00
assets First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
config Download maxmind db on boot 2026年05月26日 21:35:58 +02:00
lib Remove outdated file ( #7 ) 2026年05月27日 05:43:34 +02:00
priv Renamed voebb to Vöbb ( #8 ) 2026年05月27日 05:56:21 +02:00
test First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
.check.exs First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
.credo.exs add basic ui ( #18 ) 2021年09月16日 13:09:15 +02:00
.dockerignore First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
.env.example First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
.formatter.exs Empty phoenix project ( #4 ) 2021年08月28日 20:19:17 +02:00
.gitignore First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
CODE_OF_CONDUCT Create CODE_OF_CONDUCT 2021年08月23日 18:29:20 +02:00
docker-compose.yml First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
Dockerfile Download maxmind db on boot 2026年05月26日 21:35:58 +02:00
entrypoint.sh Download maxmind db on boot 2026年05月26日 21:35:58 +02:00
justfile First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
LICENSE First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
mix.exs First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
mix.lock First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00
README.md First Version of the real site 🥳 2026年05月26日 21:10:25 +02:00

Bookpile

Enter an ISBN, get a list of bookshops and libraries near you. No affiliate links, no cookies. AGPL-3.0-or-later.

How it works

bookpile.org/b/<isbn> looks up book metadata, detects the visitor's location from their IP address (server-side, using a local MaxMind database), and returns nearby shops and libraries. The URL works for everyone - each visitor sees results for their own location.

The list of shops and libraries lives in priv/websites.json. Pull requests to add or fix entries are welcome.

websites.json format

{
 "regions": {
 "DE": {
 // ISO 3166-1 alpha-2 country code
 "shops": [
 /* shown for every city in this country */
 ],
 "libraries": [
 /* shown for every city in this country */
 ],
 "cities": {
 "Berlin": {
 "lat": 52.52, // used for nearest-city matching
 "lng": 13.405,
 "shops": [
 /* city-specific shops */
 ],
 "libraries": [
 /* city-specific libraries */
 ],
 },
 },
 },
 },
 "everywhere": {
 // shown to all visitors regardless of location
 "shops": [],
 "libraries": [],
 "trackers": [], // book-tracking sites (Goodreads, StoryGraph, ...)
 },
}

The country code is used to look up the display name (e.g. "Germany") and flag emoji at runtime via the browser's Intl.DisplayNames API.

Each entry in any list follows the same shape:

{
 "name": "Buchbox Berlin",
 "note": "Short description shown in the UI",
 "url": "https://example.com/search?q={{ISBN}}", // {{ISBN}} or {{TITLE}}
 "tags": ["Indie"], // optional, see below
}

URL placeholders: {{ISBN}} is replaced with the book's ISBN; use {{TITLE}} for services that don't support ISBN search.

Tags control sort order within the national/everywhere shop list:

Tag Meaning
"Indie" Independent shop — sorted to the top
"International" Ships worldwide — sorted to the bottom

The full structure is defined in priv/websites_schema.json and is validated against it automatically when running mix test.

Dependencies

Running locally

# 1. Install Elixir dependencies
mix deps.get
# 2. Copy the environment template and fill in your values
cp .env.example .env
# Set BOOKPILE_GOOGLE_API_KEY in .env
# 3. Place the MaxMind database
mkdir -p priv/geo
cp /path/to/GeoLite2-City.mmdb priv/geo/
# 4. Create and migrate the database
mix ecto.setup
# 5. Start the server
mix phx.server

Visit localhost:4000.

S3 cover image caching (optional)

Set these environment variables to have book cover images downloaded and stored in S3 instead of served from external sources:

Variable Description
BOOKPILE_S3_BUCKET Bucket name — setting this enables the feature
BOOKPILE_S3_REGION Region (default: us-east-1)
BOOKPILE_S3_ENDPOINT S3 API host for non-AWS providers (e.g. fsn1.your-objectstorage.com)
BOOKPILE_S3_PUBLIC_URL Public base URL for stored files (e.g. https://my-bucket.fsn1.your-objectstorage.com) — defaults to the AWS URL pattern
AWS_ACCESS_KEY_ID Access key
AWS_SECRET_ACCESS_KEY Secret key

Covers are stored as covers/<isbn>.<ext> with public-read ACL. If the bucket variable is not set the feature is disabled and external image URLs are used as-is. Works with any S3-compatible storage (AWS, Hetzner, etc.).

Development

mix test # run tests
mix check # tests + credo + dialyzer + formatter (run before committing)
mix format # format code

Assets are bundled with esbuild - no separate Node build step needed during development.