- Update validation from minutes (15-1440) to seconds (1-3600) - Clarify behavior: real-time file watching with polling fallback - Remove scheduler references from development docs - Update migration notes for the new implementation
13 KiB
Bookhoard Development Guide
This guide is for developers contributing to Bookhoard or setting up a development environment.
🏗 Architecture
Directory Structure
bookhoard/
├── cmd/server/ # Application entry point
│ ├── main.go # Server initialization, route registration
│ └── tests/ # Integration tests (30+ test files)
├── internal/
│ ├── config/ # Configuration management
│ ├── database/ # Database layer (SQLC generated)
│ ├── handlers/ # HTTP request handlers (18 files)
│ ├── middleware/ # HTTP middleware (9 files)
│ ├── services/ # Business logic (7 files)
│ ├── sync/ # Sync framework (5 files)
│ ├── opds/ # OPDS feed generation
│ └── utils/ # Utility functions
├── templates/ # UI templates (17 .templ files)
├── web/src/ # Frontend TypeScript
├── database/schema/ # Database schema
├── docs/ # Documentation
└── bruno/ # API test collections
Backend Components
Handlers (internal/handlers/):
auth.go- Authentication & user managementlibrary.go- Library CRUD operationsscanner.go- Media scanning operationsmedia.go- Media downloads, shelveskoreader.go- KOReader sync protocolkobo.go- Kobo sync protocolcollections.go- Collection managementdevices.go- Device registration/managementconflicts.go- Sync conflict resolutionqueue.go- Sync queue managementprogress.go- Reading progress trackinganalytics.go- Usage analyticsopds.go- OPDS feed generationwebsocket.go- WebSocket connectionssync.go- Sync orchestrationbook_matching.go- Book linking/matchingsidecar.go- Sidecar file handlingrefresh_token.go- Token refresh logiccontext.go- Handler context utilities
Middleware (internal/middleware/):
device_auth.go- Device authenticationdevice_rate_limiter.go- Device-specific rate limitingerror_handler.go- Global error handlinglogin_attempts.go- Login attempt trackingpassword_validator.go- Password complexity validationrate_limiter.go- IP-based rate limitingrequest_tracing.go- Request ID trackingsecurity.go- Security headerstransaction.go- Database transaction middleware
Services (internal/services/):
library_service.go- Library operationsmedia_scanner.go- File scanning, metadata extraction, and real-time file watchingworker.go- Job queue worker poolcollection_service.go- Collection rules processingconversion_service.go- EPUB→KEPUB conversionbook_matching.go- Book matching algorithms
Sync Framework (internal/sync/):
queue.go- Sync queue processorprogress.go- Universal progress formatwebsocket.go- Real-time sync broadcastoffline.go- Offline sync supportformat.go- Format group conversion
Database Schema
Core Tables:
users- User accounts with authentication and settingslibraries- Library definitionslibrary_types- Media type definitions (ebooks, comics, manga)library_folders- Multiple folders per librarylibrary_visibility- User-specific library access controlmedia_items- Universal media storage (replaces ebooks table)media_ratings- User ratings (1-10 scale for half-star precision)media_notes- User annotationsmedia_highlights- User highlights with color customizationreading_progress- Universal progress tracking across devicesdevices- Device registry for syncsync_queue- Offline sync supportsync_conflicts- Conflict resolution trackingcollections- Device-neutral collectionscollection_items- Books in collectionsdevice_shelf_mappings- Map collections to device-specific shelvesdevice_catalogs- Track OPDS downloads and ContentId mappingskobo_shelves- Kobo-specific shelf managementreading_history- Reading session trackingunlinked_books- Track books that couldn't be auto-matchedmedia_item_formats- Track all format versions with hashesdevice_file_aliases- Track file paths per devicerefresh_tokens- JWT refresh token storage
Database Functions:
normalize_isbn()- ISBN format normalizationdetect_format_group()- Detect format group (reflowable, fixed_layout, comic_archive)convert_progress()- Convert progress between format groupsdetect_conflict()- Detect sync conflictsmerge_progress()- Merge progress from multiple sources
Technology Stack
Backend:
- Go 1.25+
- Echo v4 - HTTP framework
- pgx v5 - PostgreSQL driver
- SQLC - SQL code generation
- jwt-go - JWT authentication
- bcrypt - Password hashing
Frontend:
- Templ - HTML templating with Go
- HTMX - Dynamic interactions
- Tailwind CSS - Styling
- TypeScript - Frontend logic
Database:
- PostgreSQL 15+
- 30+ tables
- 50+ indexes
- JSONB for complex data
Testing:
- Testify - Testing framework
- Bruno - API testing
- 30+ integration test files
🚀 Local Development
Prerequisites
- Go 1.25+
- Node.js 18+ (for frontend build)
- Podman or Docker
- PostgreSQL 15+ (or use Podman)
Setup
# 1. Clone repository
git clone https://github.com/yourusername/bookhoard.git
cd bookhoard
# 2. Install Go dependencies
go mod download
# 3. Install build tools
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
go install github.com/a-h/templ/cmd/templ@latest
# 4. Set up environment
cp .env.example .env
# Edit .env with your settings
# 5. Generate database code
cd internal/database
sqlc generate
# 6. Generate templates
cd ../../templates
templ generate
# 7. Build frontend
cd ../web
npm install
npm run build
# 8. Run tests
cd ..
go test ./... -v
Running Locally
# Option 1: Using containers (recommended)
podman-compose up --build
# Option 2: Direct Go run (requires local PostgreSQL)
# Generate secure passwords (no special characters):
# JWT_SECRET: openssl rand -hex 32
# DBPASS: openssl rand -hex 16
export JWT_SECRET="your-dev-secret"
export DBPASS="your-db-password"
go run cmd/server/main.go
Development Workflow
Backend Development:
# Watch mode for Go (requires air or similar)
air
# Or manual rebuild
go build -o bookhoard cmd/server/main.go
./bookhoard
Frontend Development:
cd web
npm run dev # Watch mode for TypeScript/CSS
Database Changes:
- Edit
database/schema/schema.sql - Edit
internal/database/queries/queries.sql - Run:
cd internal/database && sqlc generate - Restart server
Template Changes:
- Edit
templates/*.templ - Run:
cd templates && templ generate - Restart server (templates auto-reload in dev mode)
🧪 Testing
Unit Tests
# Run all unit tests
go test ./... -v
# Run specific package tests
go test ./internal/handlers/... -v
# Run with coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
Integration Tests
# Run integration tests (may hit rate limits)
go test ./cmd/server/tests -v
# Run with test mode (recommended)
TEST_MODE=true RATE_LIMIT_ENABLED=false go test ./cmd/server/tests -v
# Run specific test
go test ./cmd/server/tests -run TestAuth -v
API Testing with Bruno OpenCollection YAML
# Install Bruno CLI
npm install -g @usebruno/cli
# Run all tests
bruno run
# Run specific collection
bruno run bruno/user/
bruno run bruno/sync-kobo/
Test Configuration
Environment variables for testing:
TEST_MODE=true- Enable test mode (disables rate limiting)RATE_LIMIT_ENABLED=false- Disable rate limitingREQUESTS_PER_MINUTE=1000- Increase rate limit
⚠️ WARNING: Never enable these in production!
Test Library Naming Convention
Integration tests automatically clean up libraries with "test" in the name (case-insensitive).
⚠️ IMPORTANT: Do not use "test" in library names if you want to keep them!
- Libraries containing "test" (e.g., "My Test Library", "Test Library 1") will be deleted by test cleanup
- Use names like "Development Library", "Staging Books", or "Personal" for libraries you want to keep
- This ensures your manual test data persists between test runs
📝 Code Style Guidelines
Go Code
- Follow Effective Go guidelines
- Use
gofmtfor formatting - Use
golangci-lintfor linting - Use meaningful variable and function names
- Add comments for complex business logic
- Handle errors explicitly - don't ignore them
Database Operations
- Always use sqlc-generated code - No raw SQL in handlers
- Use transactions for multi-step operations
- Handle
pgx.ErrNoRowsexplicitly - Use
pgtype.UUIDfor UUID parameters - Validate inputs before database operations
Error Handling
// Good - Explicit error handling
user, err := h.db.GetUser(c.Request().Context(), userID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
}
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "database error"})
}
// Bad - Ignoring errors
user, _ := h.db.GetUser(c.Request().Context(), userID)
Adding New Features
- Database First: Add tables/columns to
schema.sql - Generate Queries: Add to
queries.sqland runsqlc generate - Handler: Implement in
internal/handlers/ - Routes: Register in
cmd/server/main.go - Tests: Add integration test in
cmd/server/tests/ - Bruno: Add API test in
bruno/ - Docs: Update relevant documentation
API Design Principles
- RESTful naming conventions
- Consistent error responses
- Proper HTTP status codes
- JWT authentication on protected routes
- Input validation with struct tags
- Use echo.Context for request/response
🐳 Deployment
Building for Production
# Using Makefile
make build-force
# Or manually
podman-compose build --no-cache
Environment Variables
Required for production:
JWT_SECRET- 64-byte random string (generate:openssl rand -hex 32)DBPASS- Strong database password (generate:openssl rand -hex 16)BASE_URL- Public URL (e.g., https://bookhoard.example.com)
Optional:
HTTPS_PROXY- If behind reverse proxy
Note: Conversion service, rate limiting, and other operational settings have defaults in docker-compose.yml and can be overridden via .env if needed.
Performance Tuning
PostgreSQL Settings:
-- In postgresql.conf
shared_buffers = 256MB
effective_cache_size = 1GB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 2621kB
min_wal_size = 1GB
max_wal_size = 4GB
Go Settings:
- GOMAXPROCS = number of CPU cores
- Worker pool concurrency: 3 (configurable in services/worker.go)
🔍 Debugging
Enable Debug Logging
# Set environment variable
export DEBUG=true
# Or in .env
DEBUG=true
Common Issues
Database Connection Errors:
- Check PostgreSQL is running
- Verify DATABASE_HOST and DATABASE_PORT
- Check firewall settings
Rate Limiting During Development:
- Enable test mode:
TEST_MODE=true RATE_LIMIT_ENABLED=false - Or increase limit:
REQUESTS_PER_MINUTE=1000
Template Not Updating:
- Run
templ generatein templates/ directory - Restart server
Database Queries Not Working:
- Run
sqlc generatein internal/database/ - Check generated code in
queries.sql.go - Verify SQL syntax in
queries.sql
📚 Additional Resources
- Project Guidelines - Development rules and standards
- API Reference - Complete API documentation
- Troubleshooting - Deployment issues
- Go Documentation
- Echo Framework
- pgx Documentation
🤝 Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow code style guidelines
- Add tests for new features
- Ensure all tests pass
- Commit with clear messages
- Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Pull Request Checklist
- Code follows style guidelines
- Tests added/updated
- Documentation updated
- All tests passing
- No new warnings
- Commit messages are clear
Happy Coding! 🚀