### Setup Development Environment Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/README.md Commands to install Ruby, clone the repository, and initialize the database for local development. ```bash # Install Ruby 3.3.6 via rbenv brew install rbenv ruby-build rbenv install 3.3.6 # Clone and setup git clone https://github.com/Pedro-Revez-Silva/shelfarr.git cd shelfarr bundle install bin/rails db:setup # Start development server bin/dev ``` -------------------------------- ### Initialize Shelfarr Deployment Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Commands to download the example configuration and start the service. ```bash # Deploy Shelfarr mkdir shelfarr && cd shelfarr curl -O https://raw.githubusercontent.com/Pedro-Revez-Silva/shelfarr/main/docker-compose.example.yml mv docker-compose.example.yml docker-compose.yml # Edit paths in docker-compose.yml, then: docker-compose up -d # First user to register becomes admin # Access at http://localhost:5056 ``` -------------------------------- ### Docker Compose for Shelfarr Deployment Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/docs/index.html This example shows how to set up Shelfarr using Docker Compose. Remember to edit the paths in the docker-compose.yml file to match your system before starting the containers. Access Shelfarr at http://localhost:3000 after deployment. ```bash # Create directory and get compose file mkdir shelfarr && cd shelfarr curl -O https://raw.githubusercontent.com/Pedro-Revez-Silva/shelfarr/main/docker-compose.example.yml mv docker-compose.example.yml docker-compose.yml # Edit paths in docker-compose.yml, then start docker-compose up -d # Access at http://localhost:3000 ``` -------------------------------- ### Install Shelfarr with Docker Compose Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/README.md Follow these steps to set up Shelfarr using Docker. Ensure you edit the docker-compose.yml file to specify your desired paths for audiobooks, ebooks, and downloads. ```bash # 1. Create directory and download compose file mkdir shelfarr && cd shelfarr curl -O https://raw.githubusercontent.com/Pedro-Revez-Silva/shelfarr/main/docker-compose.example.yml mv docker-compose.example.yml docker-compose.yml # 2. Edit docker-compose.yml with your paths # - /path/to/audiobooks → your Audiobookshelf audiobooks folder # - /path/to/ebooks → your Audiobookshelf ebooks folder # - /path/to/downloads → your download client's completed folder # 3. Start docker-compose up -d ``` -------------------------------- ### Build Shelfarr Docker Image Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md This Dockerfile defines the steps to build the Shelfarr application image. It installs dependencies, precompiles assets, and sets up the database. ```dockerfile FROM ruby:3.2 WORKDIR /app COPY Gemfile Gemfile.lock ./ RUN bundle install COPY . . # Precompile assets RUN bundle exec rails assets:precompile # Setup database RUN bundle exec rails db:setup EXPOSE 3000 CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] ``` -------------------------------- ### Dockerfile for Shelfarr Application Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Sets up the Docker environment for the Shelfarr application, including installing Ruby, dependencies, and copying application code. ```dockerfile FROM ruby:3.3-slim WORKDIR /app # Install dependencies RUN apt-get update && apt-get install -y \ build-essential \ libsqlite3-dev \ nodejs \ npm \ && rm -rf /var/lib/apt/lists/* # Install gems COPY Gemfile Gemfile.lock ./ RUN bundle install # Copy application COPY . . ``` -------------------------------- ### Manage User Accounts and Authentication Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Handle user creation, 2FA setup, account lockout, OIDC authentication, and soft deletion. ```ruby # User creation with validation user = User.create!( username: "johndoe", name: "John Doe", password: "SecurePassword123", # Min 12 chars, mixed case + number role: :user # or :admin ) # First user automatically becomes admin # User.active.count.zero? => sets role to :admin # Two-factor authentication user.generate_otp_secret! uri = user.otp_provisioning_uri # QR code URI for authenticator app user.verify_otp("123456") # Verify TOTP code backup_codes = user.enable_otp! # Enable 2FA, returns backup codes user.verify_backup_code("ABC123") # Use backup code (one-time) user.disable_otp! # Disable 2FA # Account lockout after failed attempts user.record_failed_login!("192.168.1.1") user.locked? # => true if lockout active user.unlock_in_words # => "5 minutes" user.reset_failed_logins! # Reset on successful login # OIDC/SSO authentication user = User.from_oidc({ "provider" => "authentik", "uid" => "abc123", "info" => { "email" => "user@example.com", "name" => "John Doe" } }) # Finds existing user or creates new one if auto_create enabled # Soft delete user.soft_delete! # Sets deleted_at, destroys sessions User.active # Scope for non-deleted users ``` -------------------------------- ### Configure Path Templates Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Sets up directory and filename templates for organizing media files. These templates use variables for dynamic path generation. Ensure the output paths are writable. ```ruby SettingsService.set(:audiobook_output_path, "/audiobooks") ``` ```ruby SettingsService.set(:audiobook_path_template, "{author}/{series}/{title}") ``` ```ruby SettingsService.set(:audiobook_filename_template, "{author} - {title}") ``` ```ruby SettingsService.set(:ebook_output_path, "/ebooks") ``` ```ruby SettingsService.set(:ebook_path_template, "{author}/{title}") ``` ```ruby SettingsService.set(:ebook_filename_template, "{author} - {title}") ``` -------------------------------- ### Download Client API (qBittorrent) Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md API endpoints for interacting with the qBittorrent download client. ```APIDOC ## POST /api/v2/auth/login ### Description Logs into the qBittorrent Web UI. ### Method POST ### Endpoint /api/v2/auth/login ### Request Body - **username** (string) - Required - The username for authentication. - **password** (string) - Required - The password for authentication. ### Response #### Success Response (200) - **message** (string) - A success message, typically indicating successful login. ### Request Example ```json { "username": "your_username", "password": "your_password" } ``` ``` ```APIDOC ## POST /api/v2/torrents/add ### Description Adds a new torrent to the download client. ### Method POST ### Endpoint /api/v2/torrents/add ### Query Parameters - **urls** (string) - Optional - URL of the torrent file or details feed. ### Request Body - **torrent_file** (file) - Optional - The .torrent file to upload. - **magnet_link** (string) - Optional - The magnet link of the torrent. - **save_path** (string) - Optional - The path where the torrent should be saved. ### Response #### Success Response (200) - **message** (string) - A success message, typically indicating the torrent has been added. ### Request Example (Magnet Link) ```json { "magnet_link": "magnet:?xt=urn:btih:...", "save_path": "/downloads/completed" } ``` ### Request Example (Torrent File) ``` POST /api/v2/torrents/add?save_path=/downloads/completed Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="torrent_file"; filename="torrent.torrent" Content-Type: application/x-bittorrent [...torrent file content...] ------WebKitFormBoundary7MA4YWxkTrZu0gW-- ``` ``` ```APIDOC ## GET /api/v2/torrents/info ### Description Retrieves a list of all torrents currently managed by the download client. ### Method GET ### Endpoint /api/v2/torrents/info ### Response #### Success Response (200) - **torrents** (array) - A list of torrent objects. - **hash** (string) - The unique hash of the torrent. - **name** (string) - The name of the torrent. - **size** (integer) - The total size of the torrent in bytes. - **progress** (number) - The download progress of the torrent (0.0 to 1.0). - **state** (string) - The current state of the torrent (e.g., 'downloading', 'seeding', 'completed'). - **downloaded** (integer) - The amount of data downloaded in bytes. - **uploaded** (integer) - The amount of data uploaded in bytes. - **upspeed** (integer) - The current upload speed in bytes/second. - **downSpeed** (integer) - The current download speed in bytes/second. ### Response Example ```json [ { "hash": "abcdef1234567890", "name": "Example.Torrent.Name", "size": 1073741824, "progress": 0.95, "state": "downloading", "downloaded": 1020000000, "uploaded": 50000000, "upspeed": 10000, "downSpeed": 50000 } ] ``` ``` ```APIDOC ## GET /api/v2/torrents/properties?hash={hash} ### Description Retrieves detailed properties for a specific torrent. ### Method GET ### Endpoint /api/v2/torrents/properties ### Query Parameters - **hash** (string) - Required - The unique hash of the torrent to retrieve properties for. ### Response #### Success Response (200) - **hash** (string) - The unique hash of the torrent. - **name** (string) - The name of the torrent. - **size** (integer) - The total size of the torrent in bytes. - **pieces_number** (integer) - The total number of pieces. - **piece_size** (integer) - The size of each piece in bytes. - **files** (array) - A list of files within the torrent. - **name** (string) - The name of the file. - **size** (integer) - The size of the file in bytes. - **progress** (number) - The download progress of the file (0.0 to 1.0). ### Request Example ``` GET /api/v2/torrents/properties?hash=abcdef1234567890 ``` ### Response Example ```json { "hash": "abcdef1234567890", "name": "Example.Torrent.Name", "size": 1073741824, "pieces_number": 1000, "piece_size": 1048576, "files": [ { "name": "Example.Torrent.Name/file1.mkv", "size": 536870912, "progress": 1.0 }, { "name": "Example.Torrent.Name/file2.srt", "size": 1048576, "progress": 1.0 } ] } ``` ``` -------------------------------- ### Configure Shelfarr Environment Variables Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Define server port, sub-path deployment, authentication, and database location using environment variables. ```bash # Server configuration HTTP_PORT=80 # Internal container port RAILS_RELATIVE_URL_ROOT=/shelfarr # Sub-path deployment # Security DISABLE_AUTH=true # Disable password auth (trusted networks) # Database (SQLite by default, stored in /rails/storage) DATABASE_URL=sqlite3:///rails/storage/production.sqlite3 ``` -------------------------------- ### Configure Custom HTTP Port in Docker Compose Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/README.md Modify the docker-compose.yml to use a custom internal container port if port 80 is already in use, for example, when running behind gluetun. ```yaml environment: - HTTP_PORT=8080 ports: - "5056:8080" # Map to the custom port ``` -------------------------------- ### Integrate Download Clients Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Configure and interact with torrent and usenet clients for automated downloads. ```ruby # Supported clients: qbittorrent, decypharr, sabnzbd, nzbget, deluge, transmission client = DownloadClient.create!( name: "qBittorrent", client_type: :qbittorrent, url: "http://localhost:8080", username: "admin", password: "adminadmin", category: "shelfarr", enabled: true, priority: 1 ) # Test connection client.test_connection # => true/false # Get the adapter for direct API calls adapter = client.adapter # => DownloadClients::Qbittorrent instance # Add a torrent torrent_hash = adapter.add_torrent( "magnet:?xt=urn:btih:abc123...", save_path: "/downloads/books" ) # Check torrent status info = adapter.torrent_info(torrent_hash) # => TorrentInfo(hash: "abc123", name: "Book Title", progress: 45, state: :downloading) # List all torrents torrents = adapter.list_torrents(category: "shelfarr") # Remove torrent (optionally delete files) adapter.remove_torrent(torrent_hash, delete_files: true) # Automatic client selection client = DownloadClientSelector.for_torrent # Best available torrent client client = DownloadClientSelector.for_download(search_result) # Based on result type ``` -------------------------------- ### Manage Audiobookshelf Integration Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Configure connection settings, list libraries, manage items, and trigger library scans. ```ruby # Configure Audiobookshelf SettingsService.set(:audiobookshelf_url, "http://localhost:13378") SettingsService.set(:audiobookshelf_api_key, "your-api-token") SettingsService.set(:audiobookshelf_audiobook_library_id, "lib_abc123") SettingsService.set(:audiobookshelf_ebook_library_id, "lib_def456") # List available libraries libraries = AudiobookshelfClient.libraries libraries.each do |lib| puts "#{lib.name} (#{lib.id})" puts " Type: #{lib.media_type}" puts " Folders: #{lib.folder_paths.join(', ')}" end # Get library items items = AudiobookshelfClient.library_items("lib_abc123") # => [{ "audiobookshelf_id" => "item_1", "title" => "Book", "author" => "Author" }, ...] # Trigger library scan AudiobookshelfClient.scan_library("lib_abc123") # Find item by path item = AudiobookshelfClient.find_item_by_path("/audiobooks/Andy Weir/Project Hail Mary") # Delete item AudiobookshelfClient.delete_item(item["id"]) # Sync library cache (background job) AudiobookshelfLibrarySyncJob.perform_later ``` -------------------------------- ### Manage Application Settings Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Retrieve and update application configuration, check integration status, and list settings by category. ```ruby # Get settings with defaults prowlarr_url = SettingsService.get(:prowlarr_url) auto_select = SettingsService.get(:auto_select_enabled, default: false) languages = SettingsService.get(:enabled_languages) # => ["en", "de", "fr"] # Set settings SettingsService.set(:auto_select_enabled, true) SettingsService.set(:auto_select_min_seeders, 5) SettingsService.set(:audiobook_path_template, "{author}/{title} ({year})") # Check integration status SettingsService.prowlarr_configured? # => true/false SettingsService.audiobookshelf_configured? # => true/false SettingsService.anna_archive_configured? # => true/false SettingsService.oidc_configured? # => true/false # Get all settings by category settings = SettingsService.all_by_category # => { "indexer" => { prowlarr_url: {...}, prowlarr_api_key: {...} }, ... } ``` -------------------------------- ### Build Filename Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Generates a filename for a book based on the configured filename template. The `extension` parameter should include the leading dot (e.g., ".epub"). ```ruby filename = PathTemplateService.build_filename(book, ".epub") ``` -------------------------------- ### Background Jobs Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Documentation for the background job system using Solid Queue. ```APIDOC ## RequestQueueJob ### Description A recurring job that processes the request queue, managing searches and retries. ### Performing This job performs the following actions: 1. Processes pending requests in batches (FIFO). 2. Triggers `SearchJob` for each request. 3. Handles retries for requests that were not found. 4. Re-enqueues itself to run every 5 minutes. ### Configuration - **batch_size**: The number of requests to process per batch (default: 5). - **rate_limit_delay**: Delay in seconds between API calls to respect rate limits (default: 2). ### Code Example ```ruby class RequestQueueJob < ApplicationJob def perform # Process pending requests (FIFO) Request.pending.order(:created_at).limit(batch_size).each do |request| SearchJob.perform_later(request.id) sleep rate_limit_delay # Respect API rate limits end # Retry not_found requests that are due Request.not_found.where("next_retry_at <= ?", Time.current).each do |request| request.update!(status: :pending) # Re-queue for search end # Re-enqueue self RequestQueueJob.set(wait: 5.minutes).perform_later end private def batch_size Setting.get(:queue_batch_size, default: 5) end def rate_limit_delay Setting.get(:rate_limit_delay, default: 2) # seconds end end ``` ``` ```APIDOC ## SearchJob ### Description This job is triggered when a request reaches the front of the queue and initiates a search for the requested book. ### Performing 1. Finds the request by ID. 2. Updates the request status to 'searching'. 3. Calls the Prowlarr API to search for the book. 4. If results are found, they are stored for user selection or auto-download. 5. If no results are found, the request status is updated to 'not_found', and a retry mechanism is scheduled. ### Parameters - **request_id** (integer) - The ID of the request to process. ### Code Example ```ruby class SearchJob < ApplicationJob def perform(request_id) request = Request.find(request_id) request.update!(status: :searching) results = ProwlarrClient.search(request.book.title, request.book.author) if results.empty? # Move to back of queue for retry retry_delay = [24.hours * (request.retry_count + 1), 7.days].min request.update!( status: :not_found, retry_count: request.retry_count + 1, next_retry_at: Time.current + retry_delay ) else # Store results for user selection, or auto-download best match # based on settings end end end ``` ``` ```APIDOC ## DownloadMonitorJob ### Description A recurring job that monitors the progress of active downloads and triggers post-processing upon completion. ### Performing 1. Iterates through downloads that are in 'queued' or 'downloading' states. 2. Fetches the current status from the download client. 3. Updates the download's progress and state. 4. If a download is completed, it triggers the `PostProcessJob`. 5. Re-enqueues itself to run every 30 seconds. ### Code Example ```ruby class DownloadMonitorJob < ApplicationJob def perform Download.where(status: [:queued, :downloading]).find_each do |download| status = DownloadClient.status(download.download_client_id) download.update!(progress: status.progress, status: status.state) if status.completed? PostProcessJob.perform_later(download.id) end end # Re-enqueue self DownloadMonitorJob.set(wait: 30.seconds).perform_later end end ``` ``` ```APIDOC ## PostProcessJob ### Description Handles the renaming and moving of completed downloads to their final destination. ### Performing 1. Finds the completed download record. 2. Updates the associated request status to 'processing'. 3. Renames files according to the defined naming convention. 4. Moves the files to the appropriate output folder. 5. Updates the book's file path. 6. Updates the request status to 'completed'. ### Parameters - **download_id** (integer) - The ID of the completed download to process. ### Code Example ```ruby class PostProcessJob < ApplicationJob def perform(download_id) download = Download.find(download_id) request = download.request request.update!(status: :processing) # Rename files according to naming convention # Move to appropriate output folder # Update book.file_path for ebook download button request.book.update!(file_path: final_path) request.update!(status: :completed, completed_at: Time.current) end end ``` ``` -------------------------------- ### Create User via API Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Endpoint for creating new user accounts. Requires a JSON payload with name, username, and password. ```bash # Create a new user curl -X POST http://localhost:5056/api/v1/users \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "username": "johndoe", "password": "SecurePassword123" }' # Response (201 Created): { "id": 2, "name": "John Doe", "username": "johndoe", "role": "user", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } # Error response (422 Unprocessable Entity): { "errors": [ "Password must include at least one lowercase letter, one uppercase letter, and one number", "Password is too short (minimum is 12 characters)" ] } ``` -------------------------------- ### Build Destination Path Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Constructs the full destination path for a book using configured templates and provided book details. The `base_path` parameter specifies the root directory for media. ```ruby book = Book.new(title: "Project Hail Mary", author: "Andy Weir", year: 2021) destination = PathTemplateService.build_destination(book, base_path: "/audiobooks") ``` -------------------------------- ### Search Books with Open Library Client Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Methods for querying book metadata, work details, and edition information from Open Library. ```ruby # Search for books by query results = OpenLibraryClient.search("Project Hail Mary Andy Weir", limit: 10) results.each do |result| puts "#{result.title} by #{result.author}" puts " Work ID: #{result.work_id}" puts " Year: #{result.first_publish_year}" puts " Cover: #{result.cover_url(size: :m)}" puts " Editions: #{result.edition_count}" end # Get detailed work information work = OpenLibraryClient.work("OL20668448W") # => WorkDetails( # work_id: "OL20668448W", # title: "Project Hail Mary", # description: "A lone astronaut must save humanity...", # subjects: ["Science fiction", "Space exploration"], # covers: [12345678], # first_publish_date: "2021" # ) # Get edition details with ISBN edition = OpenLibraryClient.edition("OL32618089M") # => EditionDetails( # edition_id: "OL32618089M", # work_id: "OL20668448W", # title: "Project Hail Mary", # isbn: "9780593135204", # number_of_pages: 496 # ) # Generate cover URLs cover_url = OpenLibraryClient.cover_url(12345678, size: :l) # => "https://covers.openlibrary.org/b/id/12345678-L.jpg" ``` -------------------------------- ### Create Downloads Table Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the database table for tracking active downloads, including references to requests, download client IDs, file details, and status. Progress and size are also stored. ```ruby create_table :downloads do |t| t.references :request, null: false, foreign_key: true t.string :download_client_id # ID/hash from qBittorrent t.string :name # torrent/nzb name t.integer :status, default: 0 t.integer :progress, default: 0 # 0-100 t.bigint :size_bytes t.string :download_path t.timestamps end ``` -------------------------------- ### Integrate Prowlarr and Jackett Indexers Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Configure indexers and perform unified searches for book releases. ```ruby # Configure via SettingsService SettingsService.set(:prowlarr_url, "http://localhost:9696") SettingsService.set(:prowlarr_api_key, "your-api-key") # Search via unified IndexerClient results = IndexerClient.search("Project Hail Mary", book_type: :audiobook) results.each do |result| puts "#{result.title}" puts " Indexer: #{result.indexer}" puts " Size: #{result.size_bytes / 1024 / 1024} MB" puts " Seeders: #{result.seeders}" puts " Download: #{result.download_url}" puts " Magnet: #{result.magnet_url}" end # Prowlarr-specific features indexers = IndexerClients::Prowlarr.indexers # Filter by tags SettingsService.set(:prowlarr_tags, "books,audiobooks") # Check connection IndexerClient.test_connection # => true/false IndexerClient.configured? # => true/false ``` -------------------------------- ### Integrate Anna's Archive Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Search and download ebooks from Anna's Archive, with optional FlareSolverr support for bypassing DDoS protections. ```ruby # Configure Anna's Archive SettingsService.set(:anna_archive_enabled, true) SettingsService.set(:anna_archive_api_key, "your-member-api-key") SettingsService.set(:anna_archive_url, "https://annas-archive.se") # Optional: Configure FlareSolverr for DDoS bypass SettingsService.set(:flaresolverr_url, "http://flaresolverr:8191") # Search for ebooks results = AnnaArchiveClient.search( "Project Hail Mary", file_types: %w[epub pdf], language: "en", limit: 20 ) results.each do |result| puts "#{result.title} by #{result.author}" puts " MD5: #{result.md5}" puts " Format: #{result.file_type}" puts " Size: #{result.file_size}" puts " Year: #{result.year}" end # Get download URL (requires API key) download_url = AnnaArchiveClient.get_download_url(result.md5) # Returns direct HTTP link or torrent/magnet for the file ``` -------------------------------- ### PostProcessJob for Handling Completed Downloads Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md This job handles the post-processing of completed downloads, including renaming files, moving them to the correct output folder, and updating the book's file path. It also updates the request status to completed. ```ruby class PostProcessJob < ApplicationJob def perform(download_id) download = Download.find(download_id) request = download.request request.update!(status: :processing) # Rename files according to naming convention # Move to appropriate output folder # Update book.file_path for ebook download button request.book.update!(file_path: final_path) request.update!(status: :completed, completed_at: Time.current) end end ``` -------------------------------- ### Settings and Service Testing Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Endpoints for managing system settings and testing external service integrations. ```APIDOC ## GET /admin/settings ### Description Retrieve all system settings. ## PATCH /admin/settings/bulk_update ### Description Update multiple settings at once. ## POST /admin/settings/test_prowlarr ### Description Test Prowlarr integration. ## POST /admin/settings/test_audiobookshelf ### Description Test Audiobookshelf integration. ``` -------------------------------- ### Define the Library Folder Structure Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-plan.md The expected directory hierarchy for Audiobookshelf compatibility. ```text /audiobooks /Author Name /Series Name (if applicable) /Book Title /files... /Standalone Title (non-series) /files... /ebooks /Author Name /Title.epub ``` -------------------------------- ### Configure Webhooks Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Configures the webhook service by enabling it, setting the URL, and defining which events should trigger notifications. Ensure the webhook URL is correctly formatted. ```ruby SettingsService.set(:webhook_enabled, true) ``` ```ruby SettingsService.set(:webhook_url, "https://discord.com/api/webhooks/...") ``` ```ruby SettingsService.set(:webhook_events, "request_created,request_completed,request_failed") ``` -------------------------------- ### Check Anna Archive Configuration Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Verify if the Anna Archive client is enabled or configured with an API key. ```ruby AnnaArchiveClient.configured? # => true if enabled with API key AnnaArchiveClient.enabled? # => true if enabled (key optional) ``` -------------------------------- ### Create Uploads Table Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the database table for tracking manual file uploads, including user references, book association, status, original filename, and temporary file path. Populates book reference after processing. ```ruby create_table :uploads do |t| t.references :user, null: false, foreign_key: true t.references :book, foreign_key: true # populated after processing t.integer :status, default: 0 t.string :original_filename t.string :file_path # temporary storage before processing t.timestamps end ``` -------------------------------- ### POST /api/v1/users Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Creates a new user account in the Shelfarr system. ```APIDOC ## POST /api/v1/users ### Description Creates a new user account via the JSON API endpoint. ### Method POST ### Endpoint /api/v1/users ### Request Body - **name** (string) - Required - Full name of the user - **username** (string) - Required - Unique username for login - **password** (string) - Required - User password (minimum 12 characters, must include lowercase, uppercase, and a number) ### Request Example { "name": "John Doe", "username": "johndoe", "password": "SecurePassword123" } ### Response #### Success Response (201) - **id** (integer) - User ID - **name** (string) - User name - **username** (string) - User username - **role** (string) - User role - **created_at** (string) - ISO 8601 timestamp - **updated_at** (string) - ISO 8601 timestamp #### Response Example { "id": 2, "name": "John Doe", "username": "johndoe", "role": "user", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ``` -------------------------------- ### Create Settings Table Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the database table for application configuration settings, stored as key-value pairs with a specified value type (string, integer, boolean, json). Ensures unique keys for settings. ```ruby create_table :settings do |t| t.string :key, null: false t.text :value t.string :value_type, default: "string" # string, integer, boolean, json t.timestamps end add_index :settings, :key, unique: true ``` -------------------------------- ### Download Clients API Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Endpoints for managing download client configurations and testing connections. ```APIDOC ## GET /admin/download_clients ### Description List all configured download clients. ## POST /admin/download_clients ### Description Create a new download client. ## PATCH /admin/download_clients/:id ### Description Update a download client. ## DELETE /admin/download_clients/:id ### Description Delete a download client. ## POST /admin/download_clients/:id/test ### Description Test the connection to a specific download client. ``` -------------------------------- ### Create Requests Table Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the database table for user book requests, including fields for book and user references, status, retry information, and attention flags. Indexes are added for efficient querying. ```ruby create_table :requests do |t| t.references :book, null: false, foreign_key: true t.references :user, null: false, foreign_key: true t.integer :status, default: 0 t.text :notes # user can add context to request t.integer :retry_count, default: 0 t.datetime :next_retry_at t.datetime :completed_at t.boolean :attention_needed, default: false # flagged for admin review t.text :issue_description # why it needs attention t.timestamps end add_index :requests, :status add_index :requests, :next_retry_at add_index :requests, :attention_needed ``` -------------------------------- ### DownloadMonitorJob for Tracking Downloads Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md This recurring job monitors the progress of active downloads by querying the download client. It updates download status and triggers post-processing upon completion. It re-enqueues itself for continuous monitoring. ```ruby class DownloadMonitorJob < ApplicationJob def perform Download.where(status: [:queued, :downloading]).find_each do |download| status = DownloadClient.status(download.download_client_id) download.update!(progress: status.progress, status: status.state) if status.completed? PostProcessJob.perform_later(download.id) end end # Re-enqueue self DownloadMonitorJob.set(wait: 30.seconds).perform_later end end ``` -------------------------------- ### Create SystemHealth Table Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the database table for tracking the health status of integrated services like Prowlarr, download clients, and Audiobookshelf. Includes status, messages, and timestamps for checks. ```ruby create_table :system_health do |t| t.string :service, null: false # prowlarr, download_client, audiobookshelf t.integer :status, default: 0 t.text :message t.datetime :last_check_at t.datetime :last_success_at t.timestamps end add_index :system_health, :service, unique: true ``` -------------------------------- ### Environment Variables Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Configures Shelfarr using environment variables. `RAILS_MASTER_KEY` is required for production. `PUID` and `PGID` control file permissions, and `CHOWN_ON_START` determines ownership behavior. ```bash # Required for production RAILS_MASTER_KEY=your-64-char-hex-key # Auto-generated if not set # User/Group permissions PUID=1000 # User ID for file permissions PGID=1000 # Group ID for file permissions CHOWN_ON_START=auto # auto/always/never ``` -------------------------------- ### Manage Book Requests Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Define request models and handle the lifecycle of book requests including searching, retrying, and cancellation. ```ruby # Request statuses: pending, searching, not_found, downloading, processing, completed, failed class Request < ApplicationRecord belongs_to :book belongs_to :user has_many :downloads, dependent: :destroy has_many :search_results, dependent: :destroy enum :status, { pending: 0, searching: 1, not_found: 2, downloading: 3, processing: 4, completed: 5, failed: 6 } end # Create a new request book = Book.find_or_initialize_by_work_id("openlibrary:OL45804W", book_type: :ebook) book.update!(title: "The Martian", author: "Andy Weir") request = user.requests.create!( book: book, status: :pending, language: "en" ) # Trigger immediate search SearchJob.perform_later(request.id) # Manual result selection and download search_result = request.search_results.pending.first download = request.select_result!(search_result) # Creates Download record and enqueues DownloadJob # Retry logic with exponential backoff request.schedule_retry! # Schedules retry based on settings request.retry_now! # Immediate retry # Cancel request (removes from download client too) request.cancel! # Check states request.can_retry? # => true if retryable request.needs_manual_selection? # => true if results need review request.can_be_cancelled? # => true if not completed ``` -------------------------------- ### Execute Background Jobs Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Trigger asynchronous workflows for searching, downloading, post-processing, and monitoring. ```ruby # Search job - searches indexers and Anna's Archive SearchJob.perform_later(request.id) # Updates request status: pending -> searching # Saves search results, attempts auto-select if enabled # Schedules retry on not_found # Download job - dispatches to download client DownloadJob.perform_later(download.id) # Handles torrent/usenet/direct HTTP downloads # Updates download with external_id from client # Creates request events for diagnostics # Post-processing job - organizes files after download PostProcessingJob.perform_later(download.id) # Copies files to output directory using path templates # Renames files according to filename template # Triggers Audiobookshelf library scan # Sends completion notifications # Download monitor - checks active downloads DownloadMonitorJob.perform_later # Runs periodically to check download progress ``` -------------------------------- ### Ebook File Naming Convention Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the standard naming convention for ebook files. ```plaintext {Author}/{Title}.epub {Author}/{Title}.pdf ``` -------------------------------- ### Audiobookshelf Integration (Future) Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Details on the planned Audiobookshelf integration for delivering processed files. ```APIDOC ## Audiobookshelf API (Future) ### Description APIs for interacting with Audiobookshelf to manage libraries and trigger scans. ### Base URL User-configured (e.g., `http://localhost:8234`) ### Method GET, POST ### Endpoint - `/api/libraries` (GET) - Retrieves a list of all libraries. - `/api/libraries/{id}/scan` (POST) - Triggers a library scan. ### Headers - **Authorization** (string) - Required - Bearer token obtained from Audiobookshelf settings. ### Response #### Success Response (200) - **Libraries Endpoint**: Returns a list of library objects. - **Scan Endpoint**: Typically returns a success confirmation. ### Request Example (Trigger Scan) ``` POST /api/libraries/123/scan Authorization: Bearer YOUR_AUDIOBOOKSHELF_API_TOKEN ``` ``` -------------------------------- ### Manual Upload Job Processing Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Processes manually uploaded files, detecting book type, extracting metadata, enriching from Open Library, and creating/updating book records. ```ruby class ManualUploadJob < ApplicationJob def perform(upload_id) upload = Upload.find(upload_id) # Detect book type from file extension book_type = detect_book_type(upload.file) # Extract metadata from filename or embedded data extracted = extract_metadata(upload.file) # Enrich from Open Library if ISBN found or title/author match enriched = OpenLibraryClient.enrich(extracted) # Create or find Book record book = Book.find_or_create_by(open_library_edition_id: enriched[:edition_id]) do |b| b.assign_attributes(enriched) end # Process and move file final_path = process_and_move(upload.file, book) book.update!(file_path: final_path) upload.update!(status: :completed, book: book) end end ``` -------------------------------- ### Shelfarr Docker Compose Configuration Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md This Docker Compose file defines the services for running Shelfarr in a containerized environment. It specifies image, ports, volumes, and environment variables. ```yaml version: "3.8" services: shelfarr: image: shelfarr:latest container_name: shelfarr ports: - "3000:3000" volumes: - ./data:/app/storage # SQLite DB, uploads - /path/to/audiobooks:/audiobooks # Output for Audiobookshelf - /path/to/ebooks:/ebooks # Output for ebook reader environment: - RAILS_ENV=production - SECRET_KEY_BASE=${SECRET_KEY_BASE} restart: unless-stopped ``` -------------------------------- ### Downloads Status Enum Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the possible statuses for a download, including queued, downloading, paused, completed, and failed. Used to monitor the progress and outcome of file downloads. ```ruby enum :status, { queued: 0, downloading: 1, paused: 2, completed: 3, failed: 4 } ``` -------------------------------- ### Define Book Model Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt ActiveRecord model for managing book entities, supporting both audiobook and ebook types. ```ruby # Book types: audiobook (0) or ebook (1) class Book < ApplicationRecord enum :book_type, { audiobook: 0, ebook: 1 } has_many :requests, dependent: :restrict_with_error has_many :uploads, dependent: :nullify validates :title, presence: true validates :book_type, presence: true end # Find or create a book from metadata book = Book.find_or_initialize_by_work_id("hardcover:12345", book_type: :audiobook) book.title = "Project Hail Mary" book.author = "Andy Weir" book.cover_url = "https://covers.example.com/12345.jpg" book.year = 2021 book.save! ``` -------------------------------- ### Admin Dashboard and System Management Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Endpoints for accessing the admin dashboard, checking for updates, and triggering system health checks. ```APIDOC ## GET /admin ### Description Retrieves the admin dashboard containing system statistics. ### Method GET ### Endpoint /admin ## POST /admin/check_updates ### Description Triggers a check for new Shelfarr releases. ### Method POST ### Endpoint /admin/check_updates ## POST /admin/run_health_check ### Description Manually triggers system health checks. ### Method POST ### Endpoint /admin/run_health_check ``` -------------------------------- ### User Management API Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Endpoints for managing system users including listing, creating, updating, and deleting. ```APIDOC ## GET /admin/users ### Description List all users. ## POST /admin/users ### Description Create a new user. ## GET /admin/users/:id ### Description Show details for a specific user. ## PATCH /admin/users/:id ### Description Update a specific user. ## DELETE /admin/users/:id ### Description Delete a specific user. ``` -------------------------------- ### User Data Model Schema Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Defines the schema for the User model, including fields for name, email, password, and role. Used for managing family members who can request books. ```ruby create_table :users do |t| t.string :name, null: false t.string :email, null: false t.string :password_digest, null: false t.integer :role, default: 0 # enum: user, admin t.timestamps end add_index :users, :email, unique: true ``` -------------------------------- ### List OpenLibrary API Endpoints Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-plan.md API endpoints used for discovery and metadata retrieval. ```text - Search API: https://openlibrary.org/search.json - Works API: https://openlibrary.org/works/{id}.json - Authors API: https://openlibrary.org/authors/{id}.json - Covers: https://covers.openlibrary.org/b/id/{id}-L.jpg ``` -------------------------------- ### Health Check Job Monitoring Source: https://github.com/pedro-revez-silva/shelfarr/blob/main/shelfarr-spec.md Monitors integration health by checking Prowlarr, download clients, and output paths. Re-enqueues itself for continuous monitoring. ```ruby class HealthCheckJob < ApplicationJob def perform check_prowlarr check_download_client check_output_paths # Re-enqueue HealthCheckJob.set(wait: 5.minutes).perform_later end private def check_prowlarr health = SystemHealth.find_or_create_by(service: "prowlarr") begin response = ProwlarrClient.ping health.update!( status: :healthy, message: nil, last_check_at: Time.current, last_success_at: Time.current ) rescue => e health.update!( status: :down, message: e.message, last_check_at: Time.current ) flag_affected_requests("Prowlarr unreachable: #{e.message}") end end def check_download_client # Similar pattern end def check_output_paths # Verify audiobook/ebook output paths are writable end def flag_affected_requests(message) # Mark in-progress requests as attention_needed if integration is down end end ``` -------------------------------- ### Perform Background Jobs Source: https://context7.com/pedro-revez-silva/shelfarr/llms.txt Schedules background jobs for tasks like health checks and library synchronization. Use `perform_later` to queue jobs for asynchronous processing. ```ruby HealthCheckJob.perform_later(service: "prowlarr") ``` ```ruby AudiobookshelfLibrarySyncJob.perform_later ```