Skip to content

kararnab/LibraryZ

Repository files navigation

LibraryZ

A self-hosted, crowdsourced book catalog — with a real native client for every platform.

Run it on a Raspberry Pi or a beefy server. Catalog books, upload files in any format, preview PDFs and plain text in-app, let your community fix metadata Wikipedia-style, track personal reading shelves, and get matrix-factorization recommendations. One Go binary on the server, one Compose Multiplatform app on Android / Desktop / Web / iOS.

CI License: MIT Go 1.25 Kotlin 2.0.21 Compose Multiplatform 1.7.3 PRs welcome


Why LibraryZ?

Self-hosted book apps are great, but each one picks a lane. LibraryZ aims at the intersection:

  • Crowdsourced catalog, not single-user shelves. Anyone can submit edits to a Work's title, authors, description, or tags. A moderator queue approves or rejects them — Wikipedia for your books, scoped to your instance.
  • One UI, four platforms, all native. The same Compose Multiplatform codebase ships an Android APK, a Desktop JVM app, a Wasm web build, and an iOS app. Not a webview wrapper.

Compared to other self-hosted options:

Project Multi-user catalog Crowdsourced edits Native mobile Web UI Recommendations
Calibre-Web browser only
Kavita community apps
Audiobookshelf ✓ (audio-first)
LibraryZ ✓ (Compose MP) ✓ (ALS MF)

Screenshots

All shots are from the Desktop client (Compose Multiplatform JVM target). The same UI runs unchanged on Android, Web (Wasm), and iOS.

Browse + Work Detail (adaptive list-detail) My Library Moderator review queue
PDF preview (PagedReader) Text preview (TextReader)
Log in Upload edition

More screenshots welcome — see docs/screenshots/README.md for the capture guide and naming conventions.

Quick start (60 seconds)

git clone https://github.com/kararnab/libraryZ
cd libraryZ
docker compose up --build

That's it. The stack comes up with:

  • API on http://localhost:8080 (Go backend)
  • Postgres on :5432 (metadata)
  • MinIO on :9000 / console :9001 (S3-compatible blob store)

Smoke-test it:

curl -s localhost:8080/health
# {"status":"Healthy","time":"..."}

curl -s -X POST localhost:8080/auth/signup \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","password":"hunter2","name":"You"}'

Then point the frontend at it — jump to Running the client below.

Want a populated instance to play with? Run the seed script:

./scripts/seed.sh
# Reader login:    reader@libraryz.local  /  libraryz-demo
# Moderator login: mod@libraryz.local     /  libraryz-demo

It creates 10 real titles, a reader with a mixed-shelf personal library, and a few pending contributions in the moderator queue — exactly the state the screenshot grid above expects.

Architecture

┌──────────────────────────────────────────────────────────────┐
│                  Compose Multiplatform UI                    │
│       Android   •   Desktop   •   Web (Wasm)   •   iOS       │
│       (one codebase in frontend/composeApp/commonMain)       │
└──────────────────────────────────────────────────────────────┘
                            │  HTTPS · JSON · multipart upload
                            ▼
┌──────────────────────────────────────────────────────────────┐
│             LibraryZ backend — single Go binary              │
│   auth · catalog · contribution · library · recommendation   │
│       gorilla/mux  +  GORM  +  modular monolith              │
│   in-process implicit-ALS trainer (goroutine + ticker)       │
└──────────────────────────────────────────────────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
   PostgreSQL            Local FS  or  S3 / MinIO       (no
   (metadata, FTS,    (file blobs, sha256-addressed,    external
    rec_* tables)     content-deduped, streamed)        ML svc)

Design notes worth knowing before you contribute:

  • Modular monolith. One Go binary, domain packages under internal/. Add new functionality as a package there, not a new cmd/.
  • Two storage backends, one interface. internal/storage.Storage is implemented by Local (filesystem) and S3 (MinIO / any S3-compatible store via minio-go). Selected by LIBRARYZ_STORAGE_BACKEND=local|s3.
  • Downloads stream through the backend (GET /editions/{id}/downloadstore.Getio.Copy). No presigned URLs.
  • Recommendations are a trained model. Implicit ALS trains in-process on a ticker and writes to rec_* tables; Recommend reads from those. A content+popularity scorer handles cold-start users.
  • Full-text search is dialect-gated. Postgres tsvector + GIN index + trigger in production; LOWER(LIKE) fallback for SQLite (what the unit tests run against). Postgres-only assertions are behind //go:build postgres.
  • Reader is sealed, not format-locked. Client-side Reader (commonMain) splits into PagedReader (PDF) and TextReader (TXT). New formats are a when branch in openReader, not a new per-platform actual.
  • Kong is the edge in docker compose up. Per-IP rate limits on /auth/login (5/min), /auth/signup (3/min), edition uploads (10/hour), service-wide fallback (60/min). The libraryz container is intentionally not published to the host. See deploy/kong/kong.yml.

Deeper walkthrough — component layout, data model, storage interface, recommender pipeline, per-platform frontend shims — in ARCHITECTURE.md.

Tech stack

Backend — Go 1.25 · gorilla/mux · GORM · Postgres 16 (SQLite for tests via glebarez/sqlite) · golang-jwt · bcrypt · minio-go · gonum (for ALS).

Frontend — Kotlin 2.0.21 · Compose Multiplatform 1.7.3 · Ktor client · kotlinx.serialization · AGP 8.7.3 · PDFBox (Desktop) / PdfRenderer (Android) / pdf.js (Wasm) / PDFKit (iOS).

Ops — Docker / docker-compose · MinIO · OpenAPI 3.1 spec at openapi/libraryz.yaml.

Configuration

All via environment variables. Defaults work for docker compose up.

Variable Default
DATABASE_URL postgres://user:password@localhost:5432/libraryz?sslmode=disable
LIBRARYZ_LISTEN_ADDR :8080
LIBRARYZ_STORAGE_BACKEND local (s3 for MinIO / S3-compatible)
LIBRARYZ_STORAGE_DIR ./data/blobs (local backend only)
LIBRARYZ_S3_ENDPOINT (unset) — e.g. minio:9000, s3.amazonaws.com
LIBRARYZ_S3_ACCESS_KEY / _SECRET_KEY (unset)
LIBRARYZ_S3_BUCKET libraryz
LIBRARYZ_S3_USE_SSL false
LIBRARYZ_MAX_UPLOAD_BYTES 524288000 (500 MiB)
LIBRARYZ_REC_RETRAIN_INTERVAL 6h
LIBRARYZ_REC_FACTORS 32
LIBRARYZ_REC_ALPHA 40
JWT_SECRET (unset) — must be ≥32 bytes for cmd/libraryz; tests use a built-in dev value

Running the client

The Compose Multiplatform client lives in frontend/. You need JDK 21 and (for Android) the Android SDK. Once JAVA_HOME is set:

cd frontend

./gradlew :composeApp:run                              # Desktop window
./gradlew :composeApp:assembleDebug                    # Android APK
./gradlew :composeApp:installDebug                     # Push to device/emulator
./gradlew :composeApp:wasmJsBrowserDevelopmentRun      # Web at :8080
./gradlew :composeApp:wasmJsBrowserDistribution        # Static web bundle

iOS builds only link on macOS — they're auto-disabled on Linux/Windows.

The default base URL is wired to localhost:8080 (Desktop / Web), 10.0.2.2 (Android emulator), and a dev LAN IP (real Android device — edit BaseUrl.android.kt).

API

Full spec: openapi/libraryz.yaml.

Public

GET  /health
POST /auth/signup                  {email, password, name}
POST /auth/login                   {email, password}     -> Authorization: Bearer <jwt>
GET  /works[?limit=&offset=]
GET  /works/search?q=...           full-text search (FTS on Postgres, LIKE on SQLite)
GET  /works/{id}
GET  /editions/{id}
GET  /editions/{id}/download       streams the file
GET  /contributions[?status=pending&limit=&offset=]
GET  /contributions/{id}

Authenticated (Authorization: Bearer <jwt>)

GET  /auth/me
POST /works                                {title, authors, description, ...}
POST /works/{id}/editions                  multipart: format, language?, file
POST /works/{id}/contributions             {patch: {field: value, ...}}
GET  /me/contributions                     calling user's own contributions
GET  /me/library
GET  /me/library/{work_id}
PUT  /me/library/{work_id}                 {shelf, status, progress_percent, rating, ...}
DEL  /me/library/{work_id}
GET  /me/recommendations
POST /me/recommendations/{work_id}/dismiss

Moderator-only (promote users with UPDATE users SET is_moderator = true WHERE email = '...')

POST /contributions/{id}/approve
POST /contributions/{id}/reject

Example:

TOKEN=$(curl -si -X POST localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"a@b.com","password":"x"}' \
  | awk '/^[Aa]uthorization:[[:space:]]*[Bb]earer[[:space:]]+/ {
      sub(/^[Aa]uthorization:[[:space:]]*[Bb]earer[[:space:]]+/, "");
      sub(/[\r\n]+$/, ""); print; exit }')

curl -X POST localhost:8080/works \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"title":"Moby-Dick","authors":"Herman Melville","publication_year":1851}'

curl -X POST localhost:8080/works/<work-id>/editions \
  -H "Authorization: Bearer $TOKEN" \
  -F format=epub -F language=en -F file=@/path/to/moby-dick.epub

Testing

# Backend — in-memory SQLite, no Docker required
go test ./...

# Postgres-only paths (full-text search tsvector). DROPS catalog tables —
# never point at production.
DATABASE_URL='postgres://user:password@localhost:5432/libraryz?sslmode=disable' \
  go test -tags=postgres ./internal/catalog/...

# Recommendation offline eval (MF vs popularity baseline)
go test -tags=eval ./internal/recommendation/...

# Frontend
cd frontend && ./gradlew :composeApp:allTests

Roadmap

  • EPUB reader (HTML + CSS bundle, per-platform renderer)
  • OAuth / OIDC login
  • OPDS feed
  • iOS verification on a real macOS CI runner
  • Bulk import from Calibre / Goodreads CSV
  • Admin endpoint for moderator promotion

Contributing

PRs and issues are very welcome — see CONTRIBUTING.md for dev setup, testing expectations, and the small handful of project conventions worth knowing. Be kind: CODE_OF_CONDUCT.md.

Good first issues are labelled good first issue.

Security

For vulnerability disclosure, see SECURITY.md. Please don't open a public issue for security problems.

Changelog

Tagged-release notes live in CHANGELOG.md. The current in-flight work sits under [Unreleased] there.

License

MIT © Arnab Kar

About

A digital library based on Microservice architecture

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors