{"id":50411845,"url":"https://github.com/ryanbbrown/bookrank","last_synced_at":"2026-05-31T04:02:36.894Z","repository":{"id":320943818,"uuid":"823845849","full_name":"ryanbbrown/bookrank","owner":"ryanbbrown","description":null,"archived":false,"fork":false,"pushed_at":"2025-10-26T20:55:56.000Z","size":47797,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-26T22:27:17.143Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanbbrown.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-03T21:00:31.000Z","updated_at":"2025-10-26T20:56:00.000Z","dependencies_parsed_at":"2025-10-26T22:27:24.549Z","dependency_job_id":null,"html_url":"https://github.com/ryanbbrown/bookrank","commit_stats":null,"previous_names":["ryanbbrown/bookrank"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ryanbbrown/bookrank","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fbookrank","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fbookrank/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fbookrank/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fbookrank/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanbbrown","download_url":"https://codeload.github.com/ryanbbrown/bookrank/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fbookrank/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33718446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T04:02:34.804Z","updated_at":"2026-05-31T04:02:36.885Z","avatar_url":"https://github.com/ryanbbrown.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BookRank\n\nBookRank is \"Beli for books\", a full-stack web app for book discovery and personal library management. It uses a comparative ranking system to help users intuitively rank their books and provided AI-powered recommendations using Pinecone. The architecture consists of a Django, React frontend SPA, deployed on Digital Ocean via Docker with Nginx/Gunicorn, and integrates with AWS OpenSearch for full-text book search. The data comes from UCSD's [Goodreads Book Graph Datasets](https://cseweb.ucsd.edu/~jmcauley/datasets/goodreads.html).\n\nBookRank was previously accessible at [bookrank.ai](https://bookrank.ai), but has since been shut down. I stopped working on it because acquiring up-to-date book data would have been prohibitively expensive, and I wanted to pursue other projects.\n\n\u003cdiv align=\"center\"\u003e\n    \u003cimg src=\"homepage.png\" alt=\"homepage\" width=\"60%\"\u003e\n    \u003cbr/\u003e\n\u003c/div\u003e\n\n# Architecture\n\n## 1. Frontend Layer (React SPA)\n\nReact-based single-page application built with Vite and TypeScript. The client uses Axios for API calls with React Query for data fetching, caching, and state management.\n\n```\nreact-app/src/\n├── pages/                   # Route components (mounted by React Router)\n│   ├── Homepage.tsx              # Landing page\n│   ├── Login.tsx                 # Authentication form\n│   ├── Dashboard.tsx             # User dashboard with stats, charts, recommendations\n│   ├── MyBooks.tsx               # Personal library view (filtered by status)\n│   ├── ...\n│\n├── components/              # Reusable React components\n│   ├── ui/                       # shadcn/ui base components (button, card, dialog, etc.)\n│   ├── Header.tsx                # Navigation header\n│   ├── BookRow.tsx               # Single book list item\n│   ├── CompareModal.tsx          # Pairwise comparison interface\n│   ├── ...\n│   └── [Stats components]        # NumBooks, NumRecs, TopRecommendation, etc.\n│\n├── hooks/                   # Custom React hooks\n│   └── useBookComparison.ts      # Book comparison logic\n│\n├── types/                   # TypeScript type definitions\n│   └── types.ts                  # ApiResponse, Book, UserBook, UserAccount interfaces\n│\n├── axiosConfig.ts           # HTTP client configuration\n│\n├── App.tsx                  # Root component (routing, QueryClientProvider)\n└── index.tsx                # Entry point (React DOM mount)\n```\n\n\n## 2. Backend Layer (Django)\n\nDjango-based REST API server that handles business logic, authentication, and database operations. The backend follows a **layered architecture** where views handle HTTP endpoints, models define data structures and business logic via managers, and services integrate with external APIs.\n\n- **Views:** REST endpoints that validate requests, call model managers for business logic, and return responses\n- **Models:** Data entities with custom managers that encapsulate business logic and database operations (RLS-like filtering via user context)\n- **Services:** Classes for external API integration (OpenSearch for keyword search, Pinecone for vector similarity)\n- **Tasks:** Celery async tasks for long-running operations (CSV import)\n\n```\ndjangoapp/\n├── djangoapp/               # Main Django application\n│   ├── settings.py               # Django settings (database, Celery, CORS, sessions)\n│   ├── urls.py                   # URL routing configuration\n│   ├── wsgi.py                   # WSGI application entry point\n│   ├── celery.py                 # Celery task queue setup (Redis broker)\n│   │\n│   ├── models.py                 # Data models with custom managers\n│   │   ├── UserAccount               # Custom user model (rankings counts)\n│   │   ├── Book                      # Book catalog (work_id, title, author, etc.)\n│   │   ├── UserBook                  # User's personal library with Glicko ratings\n│   │   ├── ...\n│   │\n│   ├── views.py                  # REST API endpoints (~565 lines)\n│   │   ├── LoginView                 # POST /api/login/\n│   │   ├── UserBooksViewSet          # CRUD for /api/userbooks/\n│   │   ├── SearchView                # GET /api/search/ (OpenSearch integration)\n│   │   ├── CompareBookView           # POST/PATCH /api/compare-book/\n│   │   ├── RecommendationView        # CRUD /api/recommendations/ (Pinecone)\n│   │   ├── ...\n│   │   └── GoodreadsImportView       # POST /api/goodreads-import/ (triggers Celery)\n│   │\n│   ├── serializers.py            # DRF serializers for request/response validation\n│   │\n│   ├── services.py               # External service integrations\n│   │   ├── OpenSearchService         # AWS OpenSearch client (full-text search)\n│   │   └── PineconeService           # Pinecone vector database client\n│   │\n│   ├── tasks.py                  # Celery async tasks\n│   │\n│   ├── middleware.py             # Custom middleware\n│\n├── api/                     # API tests\n│\n└── manage.py                # Django management CLI\n```\n\n\n## 3. Data Layer (PostgreSQL)\n\nPostgreSQL database with Django ORM for persistence. Core entities:\n\n- **UserAccount** - User profiles with ranking counts (fiction/nonfiction/childrens/total)\n- **Book** - Book catalog from Goodreads (work_id, title, author, description, image_url, ratings, etc.)\n- **UserBook** - User's personal library\n  - Status: read, currently_reading, to_be_read\n  - Bucket: high, medium, low (for ranked books, mirroring Beli)\n  - Glicko rating fields: elo_rating, RD (rating deviation), is_ranked\n  - Timestamps: date_added, date_finished\n- **UserRecommendation** - AI recommendations per user (reference_book, viewed, score, outcome)\n\n\n## 4. Task Queue (Celery + Redis)\n\nCelery workers handle asynchronous background tasks using Redis as both broker and result backend. Used for users importing data from Goodreads, which requires using OpenSearch because our dataset isn't complete.\n\n**Tasks:**\n- **`process_csv(file_path, user_id)`** - Goodreads CSV import\n  - Reads uploaded CSV file\n  - Searches for each book via OpenSearch\n  - Creates UserBook entries if confidence score \u003e 20%\n  - Cleans up file after completion\n\n**Execution:**\n- Started in container via `entrypoint.sh`\n- Command: `celery -A djangoapp worker --loglevel=info -P solo --without-gossip --concurrency 1`\n\n\n## 5. AWS OpenSearch\n\nAWS-hosted OpenSearch domain used for full-text book search with fuzzy matching. Used when trying to find a book that you've read or want to read. Allows for fuzziness and uses a balanced ranking between search query relevance and book popularity (based on number of goodreads ratings).\n\n**Integration Files:**\n- `djangoapp/djangoapp/services.py` - OpenSearchService class\n- `djangoapp/djangoapp/views.py` - SearchView uses OpenSearchService.search()\n- `djangoapp/djangoapp/tasks.py` - process_csv uses search for import matching\n\n\n## 6. Pinecone Vector Database\n\nPinecone vector database provides book embeddings for similarity-based recommendations via cosine similarity; when a user rates a book in the \"high\" bucket, books with similar descriptions will be added to their recommendations feed.\n\n**Integration Files:**\n- `djangoapp/djangoapp/services.py` - PineconeService class\n- `djangoapp/djangoapp/models.py` - UserRecommendationManager.add_recommendations_from_seed()\n- `djangoapp/djangoapp/views.py` - RecommendationView (POST for adding, DELETE for dismissing)\n\n**Operations:**\n- **fetch_vector(work_id)** - Retrieve vector embedding for a single book\n- **query_similar(vector, k=10)** - Find k most similar books by cosine similarity\n- **get_books_from_pinecone(work_id)** - Get top recommendations for seed book\n- **get_work_to_remove(all_work_ids, bad_work_id)** - Find most similar unwanted recommendation to remove\n\n\n## 7. Deployment Infrastructure\n\nThis project was deployed on a Digital Ocean droplet; the Postgres database was also hosted on Digital Ocean.\n\n**Docker Multi-Stage Build:**\n1. **Stage 1 (React Build):** Node 20 base image\n\n2. **Stage 2 (Django + Services):** Python 3.10.12 base image\n   - Installs: Nginx, Redis server, Python dependencies\n   - Copies Django app and React build to `/djangoapp/static/`\n   - Configures Gunicorn systemd service\n   - Configures Nginx reverse proxy\n   - Executes entrypoint.sh on startup\n\n**Container Startup** (`entrypoint.sh`):\n1. Start Redis server\n2. Start Celery worker (solo mode, concurrency=1)\n3. Start Gunicorn (5 workers)\n4. Start Nginx (daemon off for container foreground)\n\n**Deployment Script** (`deploy.sh`):\n- Target: DigitalOcean Droplet (138.197.107.114)\n- Uses rsync to sync code files\n- SSH commands to build Docker image and restart container\n- Maps ports 80 and 443\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanbbrown%2Fbookrank","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanbbrown%2Fbookrank","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanbbrown%2Fbookrank/lists"}