libraryZ

module
v0.0.0-...-752c7b0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2026 License: MIT

README

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 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

Directories

Path Synopsis
cmd
libraryz command
internal
httpx
Package httpx holds small HTTP helpers shared across handler packages.
Package httpx holds small HTTP helpers shared across handler packages.
sanitize
Package sanitize is the L1 ingest filter for uploaded editions: cheap structural checks that the bytes match the declared format and aren't a zip bomb or polyglot.
Package sanitize is the L1 ingest filter for uploaded editions: cheap structural checks that the bytes match the declared format and aren't a zip bomb or polyglot.
pkg
db

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL