diff --git a/.github/SECRETS_SETUP.md b/.github/SECRETS_SETUP.md index 956d69e..35e2b2b 100644 --- a/.github/SECRETS_SETUP.md +++ b/.github/SECRETS_SETUP.md @@ -12,7 +12,7 @@ git tag v1.0.0 git push origin v1.0.0 ``` -Your image will be at: `ghcr.io/YOUR_USERNAME/turbovault:v1.0.0` +Your image will be at: `ghcr.io/ryankazokas/turbovault-app:v1.0.0` ## Optional: Use a Different Registry @@ -91,7 +91,7 @@ The workflow will automatically build and push to GitHub Container Registry. 1. Go to your GitHub profile 2. Click **Packages** tab 3. You'll see `turbovault` package -4. Images are at: `ghcr.io/YOUR_USERNAME/turbovault:TAG` +4. Images are at: `ghcr.io/ryankazokas/turbovault-app:TAG` ## Troubleshooting @@ -214,7 +214,7 @@ Your repository includes these workflows: ## Support If you encounter issues: -1. Check the Actions logs: `https://github.com/YOUR_USERNAME/turbovault/actions` +1. Check the Actions logs: `https://github.com/ryankazokas/turbovault-app/actions` 2. Read the error messages carefully 3. For custom registries, test login locally first 4. Open an issue if you're stuck diff --git a/README.md b/README.md index cfd3f61..291bfaf 100644 --- a/README.md +++ b/README.md @@ -1,212 +1,82 @@ # ๐ŸŽฎ TurboVault -> Your personal video game collection tracker and manager +> Your personal video game collection tracker [![Rails 8.1](https://img.shields.io/badge/Rails-8.1-red.svg)](https://rubyonrails.org/) -![CI](https://github.com/YOUR_USERNAME/turbovault/workflows/CI/badge.svg) -[![RuboCop](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop) -[![Sorbet](https://img.shields.io/badge/types-sorbet-blue.svg)](https://sorbet.org) -[![Ruby 3.3](https://img.shields.io/badge/Ruby-3.3-red.svg)](https://www.ruby-lang.org/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![CI](https://github.com/ryankazokas/turbovault-app/workflows/CI/badge.svg) -TurboVault is a modern, self-hosted web application for tracking and organizing your video game collection. Built with Rails 8 and Hotwire, it offers a fast, responsive experience for managing physical and digital games across all platforms. +Track your physical and digital game collection with automatic metadata from IGDB. -## โœจ Features +## Features -- ๐Ÿ“š **Track Physical & Digital Games** - Manage both formats with detailed metadata -- ๐ŸŽฎ **IGDB Integration** - Automatic game matching with cover art and metadata -- ๐Ÿ“Š **Collection Statistics** - Track spending, completion rates, and platform distribution -- ๐Ÿ—‚๏ธ **Collections** - Organize games into custom collections -- ๐Ÿ” **Smart Search** - Find games quickly with advanced filtering -- ๐Ÿ“ฅ **CSV Import** - Bulk import your existing collection -- ๐ŸŽจ **5 Beautiful Themes** - Light, Dark, Midnight, Retro, and Ocean -- ๐Ÿ” **RESTful API** - Programmatic access to your collection -- ๐Ÿ“ **Location Tracking** - Remember where your physical games are stored -- โญ **Ratings & Status** - Track completion status and personal ratings -- ๐Ÿ‘ฅ **Public Profiles** - Optionally share your collection with others +- ๐Ÿ“ฆ Track physical & digital games +- ๐ŸŽฏ Collections & subcollections +- ๐Ÿ” Search, filter, sort +- ๐Ÿ“Š Statistics dashboard +- ๐Ÿ“ฅ CSV bulk import +- ๐ŸŽฎ IGDB metadata matching +- ๐Ÿ”Œ RESTful API -## ๐Ÿš€ Quick Start - -### Prerequisites - -- Ruby 3.3+ -- PostgreSQL 15+ -- Node.js 18+ (for asset compilation) -- Docker (optional, for containerized development) +## Quick Start ### Local Development -1. **Clone the repository** - ```bash - git clone https://github.com/yourusername/turbovault.git - cd turbovault - ``` - -2. **Install dependencies** - ```bash - bundle install - ``` - -3. **Set up environment variables** - ```bash - cp .env.example .env - # Edit .env with your configuration - ``` - -4. **Start Docker services** - ```bash - docker-compose up -d - ``` - -5. **Set up the database** - ```bash - rails db:prepare - ``` - -6. **Start the development server** - ```bash - task dev - # or: bin/dev - ``` - -7. **Visit the app** - - App: http://localhost:3000 - - Mailpit (email testing): http://localhost:8025 - -### Demo Account - -A demo account is automatically created in development: -- **Email:** demo@turbovault.com -- **Password:** password123 - -## ๐Ÿณ Docker Deployment - -TurboVault includes Docker and Kubernetes manifests for easy deployment. - -### Kubernetes (Recommended for Production) - -TurboVault is designed for Kubernetes with automated GitHub Actions CI/CD. - -**๐Ÿ“– Documentation:** -- โญ [Quick Start Guide](docs/QUICK_START.md) - Deploy in minutes -- [Deployment Guide](docs/DEPLOYMENT.md) - Complete reference -- [Kubernetes README](k8s/README.md) - K8s details - -**๐Ÿš€ Quick Deploy:** ```bash -# 1. Push to GitHub -git push origin main +# Start services +task docker:up -# 2. Tag a release (triggers automatic build) +# Setup database +task db:setup + +# Run server +task dev +``` + +Visit http://localhost:3000 + +**Demo:** demo@turbovault.com / password123 + +### Deploy to Kubernetes + +```bash +# 1. Build & push image git tag v1.0.0 && git push origin v1.0.0 -# 3. Configure Kubernetes secrets & manifests +# 2. Configure secrets cp k8s/secrets.yaml.example k8s/secrets.yaml -# Edit secrets.yaml, deployment.yaml, configmap.yaml +# Edit with your values -# 4. Deploy +# 3. Deploy ./scripts/deploy-k8s.sh ``` -See [Quick Start Guide](docs/QUICK_START.md) for detailed steps. +See [docs/QUICK_START.md](docs/QUICK_START.md) for details. -### Docker Compose (Development/Testing) +## Tech Stack + +- Ruby on Rails 8.1 with Sorbet types +- PostgreSQL with Row Level Security +- Hotwire (Turbo + Stimulus) +- Tailwind CSS +- Solid Queue for background jobs + +## Documentation + +- [Quick Start](docs/QUICK_START.md) - Getting started guide +- [Development](docs/DEVELOPMENT_GUIDE.md) - Local development +- [API Docs](docs/API_DOCUMENTATION.md) - RESTful API reference +- [Deployment](docs/DEPLOYMENT.md) - Production deployment + +## Commands ```bash -docker-compose -f docker-compose.prod.yml up -d +task dev # Start dev server +task test # Run tests +task lint # Check code style +task lint:fix # Auto-fix style issues +task typecheck # Run type checker ``` -## ๐Ÿ”ง Configuration +## License -### IGDB Integration (Optional) - -To enable automatic game metadata matching: - -1. Create a Twitch developer account at https://dev.twitch.tv -2. Register an application to get your Client ID and Secret -3. Add to `.env`: - ```bash - IGDB_CLIENT_ID=your_client_id - IGDB_CLIENT_SECRET=your_client_secret - ``` - -IGDB sync is enabled by default for all users and runs every 30 minutes. - -## ๐Ÿ“š Documentation - -**All documentation is in the [`docs/`](docs/) folder.** - -**Quick Links:** -- โญ [Quick Start](docs/QUICK_START.md) - Deploy in minutes -- ๐Ÿš€ [Deployment Guide](docs/DEPLOYMENT.md) - Complete reference -- ๐Ÿ’ป [Development Guide](docs/DEVELOPMENT_GUIDE.md) - Local development -- ๐Ÿ“– [API Documentation](docs/API_DOCUMENTATION.md) - RESTful API -- ๐ŸŽฎ [IGDB Integration](docs/IGDB_INTEGRATION.md) - Game metadata -- ๐ŸŽจ [Themes](docs/THEMES.md) - Customization -- ๐Ÿ”ท [Sorbet Types](docs/SORBET.md) - Type checking guide -- ๐ŸŽฏ [Demo Account](docs/DEMO_ACCOUNT.md) - Try it out - -[**See all documentation โ†’**](docs/README.md) - -## ๐Ÿ› ๏ธ Tech Stack - -- **Framework:** Ruby on Rails 8.1 with Sorbet type checking -- **Frontend:** Hotwire (Turbo + Stimulus) -- **Styling:** Tailwind CSS -- **Database:** PostgreSQL with Row Level Security -- **Background Jobs:** Solid Queue -- **Deployment:** Docker, Kubernetes -- **APIs:** IGDB (game metadata) - -## ๐Ÿค– Continuous Integration - -TurboVault includes GitHub Actions workflows: - -- **CI Pipeline** - Runs tests, linting, and security scans on every push -- **Build & Push** - Automatically builds Docker images and pushes to GitHub Container Registry - -### Setup GitHub Actions - -**No setup required!** The default workflow uses GitHub Container Registry (ghcr.io), which works out of the box with no secrets needed. - -Just push a tag: -```bash -git tag v1.0.0 -git push origin v1.0.0 -``` - -Your image will be at: `ghcr.io/your-username/turbovault:v1.0.0` - -**Want to use a different registry?** See [.github/SECRETS_SETUP.md](.github/SECRETS_SETUP.md) for examples (Docker Hub, private registry, etc.) - -## ๐Ÿค Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add some amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -All PRs will automatically run CI tests. - -## ๐Ÿ“ License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## ๐Ÿ™ Acknowledgments - -- Game data provided by [IGDB](https://www.igdb.com/) -- Built with [Ruby on Rails](https://rubyonrails.org/) -- UI powered by [Tailwind CSS](https://tailwindcss.com/) - -## ๐Ÿ“ง Support - -- ๐Ÿ“– [Documentation](docs/) -- ๐Ÿ› [Issue Tracker](https://github.com/yourusername/turbovault/issues) -- ๐Ÿ’ฌ [Discussions](https://github.com/yourusername/turbovault/discussions) - ---- - -Made with โค๏ธ for gamers and collectors +MIT - See [LICENSE](LICENSE) diff --git a/docs/CI_PIPELINE.md b/docs/CI_PIPELINE.md deleted file mode 100644 index 7c0bf65..0000000 --- a/docs/CI_PIPELINE.md +++ /dev/null @@ -1,307 +0,0 @@ -# CI/CD Pipeline Documentation - -TurboVault uses GitHub Actions for continuous integration and quality assurance. - -## Pipeline Overview - -The CI pipeline runs on: -- โœ… Every push to `main` or `develop` branches -- โœ… Every pull request to `main` or `develop` - -## Jobs - -### 1. Linting & Security ๐Ÿ” - -**Purpose:** Code style and security checks - -**Steps:** -1. **RuboCop** - Ruby style guide enforcement - - Uses `rubocop-rails-omakase` (Rails official style guide) - - Runs with `--parallel` for speed - - **Fails build** if style violations found - -2. **Brakeman** - Security vulnerability scanner - - Scans for Rails security issues - - `continue-on-error: true` (warnings don't block PRs) - -**Run locally:** -```bash -task lint # Check style -task lint:fix # Auto-fix issues -task security # Run Brakeman -``` - -### 2. Type Checking ๐Ÿ”ท - -**Purpose:** Static type checking with Sorbet - -**Steps:** -1. Generate RBI files for gems and Rails DSLs -2. Run Sorbet type checker -3. **Fails build** if type errors found - -**Run locally:** -```bash -task tapioca:all # Generate RBI files -task typecheck # Run type checker -``` - -**Note:** RBI generation can take 1-2 minutes in CI. - -### 3. Tests ๐Ÿงช - -**Purpose:** Run test suite - -**Services:** -- PostgreSQL 15 database - -**Steps:** -1. Set up PostgreSQL service -2. Install system dependencies -3. Create test database -4. Load schema -5. Run test suite - -**Run locally:** -```bash -task test # Run all tests -``` - -### 4. Docker Build ๐Ÿณ - -**Purpose:** Verify Docker image builds successfully - -**Steps:** -1. Set up Docker Buildx -2. Build production Docker image -3. Test that image runs - -**Run locally:** -```bash -docker build -t turbovault:test . -docker run --rm turbovault:test bundle exec ruby --version -``` - -## CI Workflow File - -Location: `.github/workflows/ci.yml` - -## Status Badges - -Status badges in `README.md`: - -```markdown -![CI](https://github.com/YOUR_USERNAME/turbovault/workflows/CI/badge.svg) -[![RuboCop](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop) -[![Sorbet](https://img.shields.io/badge/types-sorbet-blue.svg)](https://sorbet.org) -``` - -**Don't forget** to replace `YOUR_USERNAME` with your actual GitHub username! - -## What Fails the Build? - -| Check | Fails Build? | Why | -|-------|--------------|-----| -| RuboCop style issues | โœ… Yes | Code style should be consistent | -| Brakeman security warnings | โŒ No | Some warnings are false positives | -| Sorbet type errors | โœ… Yes | Type safety is important | -| Test failures | โœ… Yes | Tests must pass | -| Docker build failure | โœ… Yes | Must be deployable | - -## Fixing CI Failures - -### RuboCop Failures - -```bash -# Auto-fix most issues -task lint:fix - -# Check what's left -task lint - -# Commit fixes -git add . -git commit -m "Fix RuboCop style issues" -``` - -### Sorbet Type Errors - -```bash -# Regenerate RBI files (if gems changed) -task tapioca:all - -# Check for errors -task typecheck - -# Fix type errors in code -# See docs/SORBET.md for patterns -``` - -### Test Failures - -```bash -# Run tests locally -task test - -# Run specific test -rails test test/models/game_test.rb - -# Fix tests and re-run -``` - -### Docker Build Failures - -```bash -# Build locally to debug -docker build -t turbovault:test . - -# Check build logs for errors -# Usually missing dependencies or incorrect Dockerfile -``` - -## CI Performance - -Typical run times: -- **Linting & Security:** ~30-60 seconds -- **Type Checking:** ~2-3 minutes (RBI generation) -- **Tests:** ~1-2 minutes -- **Docker Build:** ~3-5 minutes - -**Total:** ~7-11 minutes per run - -## Optimizations - -### Caching - -We use `bundler-cache: true` in Ruby setup to cache gems: -```yaml -- uses: ruby/setup-ruby@v1 - with: - bundler-cache: true -``` - -This speeds up runs by ~1-2 minutes. - -### Parallel Execution - -Jobs run in parallel, not sequentially: -- Linting, Type Checking, Tests, and Docker Build all run simultaneously -- Total time = slowest job (usually Docker Build) - -### RuboCop Parallel - -RuboCop uses `--parallel` to check files concurrently: -```bash -bundle exec rubocop --parallel -``` - -## Local Development Workflow - -Before pushing: - -```bash -# 1. Fix style -task lint:fix - -# 2. Check types -task typecheck - -# 3. Run tests -task test - -# 4. Commit -git add . -git commit -m "Your changes" -git push -``` - -This ensures CI will pass! - -## Skipping CI - -To skip CI on a commit (e.g., documentation only): - -```bash -git commit -m "Update README [skip ci]" -``` - -## CI for Pull Requests - -When someone submits a PR: -1. CI runs automatically -2. All jobs must pass before merge -3. Status shown in PR page -4. Merge button disabled until CI passes - -## Viewing CI Results - -**On GitHub:** -1. Go to repository -2. Click **Actions** tab -3. Click on a workflow run -4. View each job's logs - -**In Pull Requests:** -- See status checks at bottom of PR -- Click "Details" to view logs - -## Troubleshooting - -### "RBI generation failed" - -**Cause:** Tapioca couldn't generate RBI files - -**Fix:** Usually transient - retry workflow. If persistent, check Gemfile.lock is committed. - -### "Database connection failed" - -**Cause:** PostgreSQL service not ready - -**Fix:** Already handled with health checks: -```yaml -options: >- - --health-cmd pg_isready - --health-interval 10s -``` - -### "Bundler version mismatch" - -**Cause:** Different Bundler versions locally vs CI - -**Fix:** Run `bundle update --bundler` locally - -## Advanced: Matrix Builds (Future) - -To test multiple Ruby versions: - -```yaml -strategy: - matrix: - ruby-version: ['3.2', '3.3', '3.4'] -``` - -Currently we only test Ruby 3.3. - -## Security - -### Secrets - -We use GitHub Secrets for sensitive data: -- `RAILS_MASTER_KEY` (optional, for encrypted credentials) -- No other secrets needed for tests - -### Dependabot - -Consider enabling Dependabot to auto-update dependencies: -- Settings โ†’ Security โ†’ Dependabot - -## Resources - -- [GitHub Actions Docs](https://docs.github.com/en/actions) -- [RuboCop](https://github.com/rubocop/rubocop) -- [Sorbet](https://sorbet.org/) -- [Brakeman](https://brakemanscanner.org/) - ---- - -**Questions?** Check the workflow file: `.github/workflows/ci.yml` diff --git a/docs/DEMO_ACCOUNT.md b/docs/DEMO_ACCOUNT.md deleted file mode 100644 index f98785e..0000000 --- a/docs/DEMO_ACCOUNT.md +++ /dev/null @@ -1,238 +0,0 @@ -# Demo Account for Development ๐ŸŽฎ - -## Quick Login - -**Development Demo Account:** -``` -Email: demo@turbovault.com -Password: password123 -``` - -Visit: http://localhost:3000/login - -## What's Included - -The demo account is automatically created when you run `task setup` or `task db:seed` in development mode. - -### Sample Data - -**12 Games** across multiple platforms: -- Nintendo 64: Ocarina of Time, Super Mario 64, GoldenEye 007 -- SNES: Super Metroid -- Nintendo Switch: Breath of the Wild, Hades, Stardew Valley -- PlayStation 5: Elden Ring, Cyberpunk 2077 -- PlayStation 2: Final Fantasy VII -- PC: Hollow Knight, Portal 2 - -**4 Collections:** -- **Nintendo Games** - All Nintendo platform games (root collection) - - **N64 Classics** - Best N64 games (subcollection) -- **All-Time Favorites** - Top-rated games -- **To Play** - Backlog games - -**Various Data:** -- โœ… Different completion statuses (Completed, Playing, Backlog, On Hold) -- โœ… Physical games with conditions (CIB, Loose, Sealed) -- โœ… Digital games with store info (Steam, PlayStation Store, eShop) -- โœ… User ratings (1-5 stars) -- โœ… Storage locations (Shelf A, Shelf B) -- โœ… Purchase prices ($9.99 - $85.00) -- โœ… Notes and descriptions -- โœ… Games organized in multiple collections - -## When to Use Demo Account - -### Good For: -- โœ… Quick testing during development -- โœ… Demonstrating features -- โœ… Testing bulk operations (already has games to select) -- โœ… Verifying collection organization -- โœ… Testing filters and search (has varied data) -- โœ… Screenshot/documentation creation - -### Create Your Own Account For: -- Testing registration flow -- Testing empty state UI -- Testing first-time user experience -- Building your actual collection - -## How It Works - -### First Time Setup -```bash -task setup -``` - -This runs `rails db:seed` which creates: -1. Platforms (31 gaming platforms) -2. Genres (30 game genres) -3. Demo user -4. Demo collections -5. Sample games - -### Subsequent Runs -The seed file is smart: -- Won't duplicate the demo user if it exists -- Won't duplicate games if demo user has any -- Safe to run `rails db:seed` multiple times - -### Reset Demo Data -If you want to reset the demo account: - -```bash -# Option 1: Reset entire database -task db:reset -# This drops, creates, migrates, and re-seeds everything - -# Option 2: Delete just the demo user -rails console -> User.find_by(email: 'demo@turbovault.com')&.destroy -> exit -rails db:seed -``` - -## Credentials Shown - -The demo credentials are displayed: - -1. **In the terminal** when you run `task dev` -2. **On the login page** (development only - green box) -3. **After seeding** - shows confirmation - -## Security Note - -**Development Only:** -- The demo account is ONLY created in development mode -- Will NOT be created in production -- Safe to commit seed file to git - -**Production:** -```ruby -if Rails.env.development? - # Demo user only created here -end -``` - -## API Testing - -The demo user can also be used for API testing: - -```bash -# 1. Create an API token via the web UI -# Login as demo@turbovault.com โ†’ Settings โ†’ API Tokens - -# 2. Or create via console -rails console -> user = User.find_by(email: 'demo@turbovault.com') -> token = user.api_tokens.create!(name: "Testing") -> puts token.token -``` - -## Tips - -### View All Demo Data -```ruby -rails console -> demo = User.find_by(email: 'demo@turbovault.com') -> puts "Games: #{demo.games.count}" -> puts "Collections: #{demo.collections.count}" -> demo.games.each { |g| puts "- #{g.title} (#{g.platform.name})" } -``` - -### Quick Links After Login -- Dashboard: http://localhost:3000/dashboard -- All Games: http://localhost:3000/games -- Collections: http://localhost:3000/collections -- Settings: http://localhost:3000/settings - -### Test Bulk Operations -1. Login as demo user -2. Go to Games list -3. Select multiple games (checkboxes appear) -4. Click "Bulk Edit" button -5. Update collections, status, location, etc. - -### Test Filters -The demo data includes games with: -- Multiple platforms (N64, SNES, Switch, PS5, PS2, PC) -- Multiple genres (Action, Adventure, RPG, Platformer, etc.) -- All completion statuses -- Both physical and digital formats -- Various ratings - -Perfect for testing search and filter functionality! - -## Troubleshooting - -### Demo user not created? -```bash -# Check environment -rails console -> Rails.env -=> "development" # Should be development - -# Re-run seeds -rails db:seed -``` - -### Demo user exists but no games? -```bash -rails console -> demo = User.find_by(email: 'demo@turbovault.com') -> demo.games.destroy_all # Clear existing -> exit - -rails db:seed # Re-create sample games -``` - -### Can't login? -- Email: `demo@turbovault.com` (exact spelling) -- Password: `password123` (all lowercase) -- Make sure database is migrated: `rails db:migrate` - -### Want fresh demo data? -```bash -task db:reset -# Drops DB, recreates, migrates, seeds everything fresh -``` - -## Sample Collection Structure - -``` -Nintendo Games (5 games) -โ”œโ”€ N64 Classics (3 games) -โ”‚ โ”œโ”€ The Legend of Zelda: Ocarina of Time -โ”‚ โ”œโ”€ Super Mario 64 -โ”‚ โ””โ”€ GoldenEye 007 -โ”œโ”€ Breath of the Wild -โ”œโ”€ Hades -โ”œโ”€ Stardew Valley -โ””โ”€ Super Metroid - -All-Time Favorites (9 games) -โ”œโ”€ Ocarina of Time -โ”œโ”€ Super Mario 64 -โ”œโ”€ Elden Ring -โ”œโ”€ Breath of the Wild -โ”œโ”€ Super Metroid -โ”œโ”€ Hollow Knight -โ”œโ”€ Portal 2 -โ”œโ”€ Hades -โ””โ”€ Stardew Valley - -To Play (2 games) -โ”œโ”€ Final Fantasy VII -โ””โ”€ Cyberpunk 2077 -``` - -## Summary - -The demo account gives you a fully populated TurboVault instance instantly: -- โœ… 12 diverse games -- โœ… 4 collections with relationships -- โœ… Realistic data (prices, locations, notes) -- โœ… All completion statuses represented -- โœ… Both physical and digital games -- โœ… Perfect for testing and demos - -Just run `task dev` and login! ๐Ÿš€ diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index d46b5ee..4c1762e 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,111 +1,68 @@ -# TurboVault Deployment Guide +# Deployment Guide -Complete guide for deploying TurboVault to production. +Deploy TurboVault to Kubernetes. -## Table of Contents +## Prerequisites -1. [GitHub Setup](#github-setup) -2. [Kubernetes Deployment](#kubernetes-deployment) -3. [Database Setup](#database-setup) -4. [DNS & SSL](#dns--ssl) -5. [Monitoring](#monitoring) +- Kubernetes cluster +- kubectl configured +- PostgreSQL database (already set up) ---- - -## GitHub Setup - -### Push to GitHub +## Quick Deploy ```bash -# Run the automated setup script -./scripts/setup-github.sh +# 1. Build image (auto-builds via GitHub Actions) +git tag v1.0.0 +git push origin v1.0.0 -# Or manually: -git init -git add . -git commit -m "Initial commit" -git branch -M main -git remote add origin https://github.com/YOUR_USERNAME/turbovault.git -git push -u origin main -``` +# 2. Configure secrets +cp k8s/secrets.yaml.example k8s/secrets.yaml +nano k8s/secrets.yaml -### Set Up GitHub Actions (Optional) - -Create `.github/workflows/ci.yml` for automated testing and building: - -```yaml -name: CI - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.3' - bundler-cache: true - - run: bundle exec rails db:test:prepare - - run: bundle exec rails test -``` - ---- - -## Kubernetes Deployment - -### Prerequisites - -- k3s/k8s cluster running -- `kubectl` configured -- Docker installed -- PostgreSQL database (in-cluster or external) - -### Quick Deploy - -```bash -# Automated deployment +# 3. Deploy ./scripts/deploy-k8s.sh ``` -### Manual Deployment +## Configuration + +### 1. Update Image + +Edit `k8s/deployment.yaml` and `k8s/migrate-job.yaml`: + +```yaml +image: ghcr.io/ryankazokas/turbovault-app:v1.0.0 +``` + +### 2. Database Connection + +Edit `k8s/configmap.yaml`: + +```yaml +DATABASE_HOST: "your-postgres-host" +DATABASE_NAME: "turbovault_production" +DATABASE_USERNAME: "turbovault" +``` + +### 3. Secrets + +Edit `k8s/secrets.yaml`: ```bash -# 1. Login to Gitea registry -docker login gitea.example.com - -# 2. Build and push Docker image -docker build -t gitea.example.com/username/turbovault:latest . -docker push gitea.example.com/username/turbovault:latest - -# 3. Create Gitea registry secret in k8s -kubectl create secret docker-registry gitea-registry \ - --docker-server=gitea.example.com \ - --docker-username=your-username \ - --docker-password=your-gitea-token \ - --docker-email=your-email@example.com \ - --namespace=turbovault - -# 2. Create secrets -cp k8s/secrets.yaml.example k8s/secrets.yaml -# Edit k8s/secrets.yaml with your values - -# 3. Generate Rails secret +# Generate Rails secret rails secret -# Copy output to k8s/secrets.yaml SECRET_KEY_BASE -# 4. Deploy to k8s +# Add to secrets.yaml: +SECRET_KEY_BASE: "output_from_rails_secret" +DATABASE_PASSWORD: "your_postgres_password" + +# Optional (for IGDB): +IGDB_CLIENT_ID: "your_igdb_client_id" +IGDB_CLIENT_SECRET: "your_igdb_client_secret" +``` + +## Manual Deployment + +```bash kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/configmap.yaml kubectl apply -f k8s/secrets.yaml @@ -116,117 +73,27 @@ kubectl apply -f k8s/service.yaml kubectl apply -f k8s/ingress.yaml ``` -### Update Image Reference - -Edit `k8s/deployment.yaml` and `k8s/migrate-job.yaml` with your registry path: - -```yaml -# For public registries (GitHub Container Registry, Docker Hub public) -# No imagePullSecrets needed! -image: ghcr.io/your-username/turbovault:latest - -# For private registries, add imagePullSecrets: -imagePullSecrets: -- name: registry-secret - -image: your-registry.com/turbovault:latest -``` - -**Default:** Use GitHub Container Registry (ghcr.io) - free and built-in. -**See:** [k8s/README.md](../k8s/README.md) for registry setup details. - ---- - -## Database Setup - -### Option 1: External PostgreSQL - -Use an external PostgreSQL instance (recommended for production): - -1. Create database and user: - ```sql - CREATE DATABASE turbovault_production; - CREATE USER turbovault WITH PASSWORD 'your-secure-password'; - GRANT ALL PRIVILEGES ON DATABASE turbovault_production TO turbovault; - ``` - -2. Update `k8s/configmap.yaml`: - ```yaml - DATABASE_HOST: "your-postgres-host.example.com" - DATABASE_PORT: "5432" - DATABASE_NAME: "turbovault_production" - DATABASE_USERNAME: "turbovault" - ``` - -3. Update `k8s/secrets.yaml`: - ```yaml - DATABASE_PASSWORD: "your-secure-password" - ``` - -### Option 2: In-Cluster PostgreSQL - -Deploy PostgreSQL in your cluster using Helm: +## Update Deployment ```bash -# Add Bitnami repo -helm repo add bitnami https://charts.bitnami.com/bitnami -helm repo update +# Build new version +git tag v1.1.0 +git push origin v1.1.0 -# Install PostgreSQL -helm install postgres bitnami/postgresql \ - --namespace turbovault \ - --set auth.database=turbovault_production \ - --set auth.username=turbovault \ - --set auth.password=changeme \ - --set primary.persistence.size=10Gi +# Update deployment +kubectl set image deployment/turbovault \ + turbovault=ghcr.io/ryankazokas/turbovault-app:v1.1.0 \ + -n turbovault -# Connection details -DATABASE_HOST: postgres-postgresql -DATABASE_PORT: 5432 +# Watch rollout +kubectl rollout status deployment/turbovault -n turbovault ``` ---- +## SSL/TLS -## DNS & SSL +Update `k8s/ingress.yaml` with your domain and TLS configuration. -### Configure DNS - -Point your domain to your cluster's ingress: - -```bash -# Get ingress IP -kubectl get ingress -n turbovault - -# Add A record -turbovault.example.com -> YOUR_INGRESS_IP -``` - -### Enable SSL with cert-manager - -```bash -# Install cert-manager -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml - -# Create ClusterIssuer -cat < /backup/turbovault-$(date +%Y%m%d).sql.gz" +# Check status +kubectl get pods -n turbovault + +# View logs +kubectl logs -f -l app=turbovault -n turbovault + +# Check resources +kubectl top pods -n turbovault ``` -### Full Backup +## Troubleshooting +### View pod details ```bash -# Backup all k8s resources -kubectl get all -n turbovault -o yaml > turbovault-backup.yaml - -# Backup secrets (encrypted) -kubectl get secrets -n turbovault -o yaml > secrets-backup.yaml +kubectl describe pod -l app=turbovault -n turbovault ``` ---- +### Test database connection +```bash +kubectl exec -it deployment/turbovault -n turbovault -- \ + rails runner "puts ActiveRecord::Base.connection.execute('SELECT 1').first" +``` -## Security Best Practices - -1. โœ… Use Kubernetes Secrets (or Sealed Secrets) -2. โœ… Enable HTTPS/TLS -3. โœ… Set resource limits -4. โœ… Use non-root container user -5. โœ… Enable Network Policies -6. โœ… Regular security updates -7. โœ… Database backups -8. โœ… Monitor logs - ---- - -## Additional Resources - -- [Kubernetes Documentation](https://kubernetes.io/docs/) -- [k3s Documentation](https://docs.k3s.io/) -- [Rails Deployment Guide](https://guides.rubyonrails.org/configuring.html) -- [TurboVault API Docs](API_DOCUMENTATION.md) - ---- - -## Support - -Need help? -- ๐Ÿ“– [Full Documentation](../README.md) -- ๐Ÿ› [Report Issues](https://github.com/yourusername/turbovault/issues) -- ๐Ÿ’ฌ [Discussions](https://github.com/yourusername/turbovault/discussions) +### View environment +```bash +kubectl exec -it deployment/turbovault -n turbovault -- env | grep DATABASE +``` diff --git a/docs/DEVELOPMENT_GUIDE.md b/docs/DEVELOPMENT_GUIDE.md index 4897b42..f81d8ae 100644 --- a/docs/DEVELOPMENT_GUIDE.md +++ b/docs/DEVELOPMENT_GUIDE.md @@ -1,684 +1,124 @@ -# TurboVault - Development Guide +# Development Guide -## Quick Start +## Prerequisites + +- Ruby 3.3+ +- Docker + +## Setup ```bash -# Start PostgreSQL +# Clone repository +git clone https://github.com/ryankazokas/turbovault-app.git +cd turbovault + +# Start services (PostgreSQL + Mailpit) task docker:up -# Setup database (first time only) +# Setup database task db:setup -# Start Rails server -task server - -# Or use bin/dev for Tailwind watch mode -bin/dev +# Run development server +task dev ``` -Visit http://localhost:3000 and create an account! +Visit http://localhost:3000 -## Development Workflow +**Demo:** `demo@turbovault.com` / `password123` -### Daily Development +## Commands ```bash -# Start all services -task docker:up # PostgreSQL -bin/dev # Rails server + Tailwind watcher +# Development +task dev # Start Rails + CSS watcher +task server # Rails only +task console # Rails console + +# Database +task db:migrate # Run migrations +task db:rollback # Rollback migration +task db:reset # Reset database +task db:seed # Load seed data + +# Testing +task test # Run tests +task test:system # System tests + +# Code Quality +task lint # Check style +task lint:fix # Auto-fix style +task typecheck # Type checker +task security # Security scan + +# Type Checking +task tapioca:init # First time only +task tapioca:all # After bundle install +task typecheck:watch # Watch mode + +# Services +task docker:up # Start services +task docker:down # Stop services +task docker:logs # View logs + +# IGDB +task igdb:sync # Manual sync +task igdb:status # Check status +task igdb:clear # Clear stuck jobs + +# Cleanup +task clean # Remove tmp/logs ``` -### Database Operations - -```bash -# Create a new migration -rails generate migration AddFieldToModel field:type - -# Run migrations -task db:migrate - -# Rollback last migration -task db:rollback - -# Reset database (drops, creates, migrates, seeds) -task db:reset - -# Open Rails console -task console - -# Check pending migrations -rails db:migrate:status -``` - -### Generating Code - -```bash -# Generate a model -rails generate model ModelName field:type - -# Generate a controller -rails generate controller ControllerName action1 action2 - -# Generate a scaffold (model + controller + views) -rails generate scaffold ModelName field:type - -# Destroy generated code -rails destroy model ModelName -``` - -### Running Tests - -```bash -# Run all tests -task test - -# Run specific test file -rails test test/models/game_test.rb - -# Run specific test -rails test test/models/game_test.rb:10 - -# Run system tests -task test:system -``` - -### Code Quality - -```bash -# Run RuboCop linter -task lint - -# Auto-fix RuboCop issues -task lint:fix - -# Security checks -task security -``` - -### Type Checking with Sorbet - -TurboVault uses Sorbet for gradual static type checking. - -```bash -# First time setup (after bundle install) -task tapioca:init -task tapioca:all - -# Run type checker -task typecheck - -# Watch mode (re-checks on file changes) -task typecheck:watch - -# Update type definitions after gem/model changes -task tapioca:gems # After bundle install -task tapioca:dsl # After model changes -``` - -**See [SORBET.md](SORBET.md) for complete type checking guide.** - ## Project Structure ``` app/ -โ”œโ”€โ”€ controllers/ -โ”‚ โ”œโ”€โ”€ concerns/ # Shared controller modules -โ”‚ โ”œโ”€โ”€ api/v1/ # API controllers -โ”‚ โ””โ”€โ”€ *.rb # Web controllers -โ”œโ”€โ”€ models/ -โ”‚ โ”œโ”€โ”€ concerns/ # Shared model modules -โ”‚ โ””โ”€โ”€ *.rb # ActiveRecord models -โ”œโ”€โ”€ views/ -โ”‚ โ”œโ”€โ”€ layouts/ # Application layouts -โ”‚ โ””โ”€โ”€ */ # Controller-specific views -โ”œโ”€โ”€ helpers/ # View helpers -โ”œโ”€โ”€ mailers/ # Email mailers -โ””โ”€โ”€ jobs/ # Background jobs +โ”œโ”€โ”€ controllers/ # Controllers +โ”œโ”€โ”€ models/ # Models +โ”œโ”€โ”€ views/ # ERB templates +โ”œโ”€โ”€ javascript/ # Stimulus controllers +โ”œโ”€โ”€ services/ # Service objects (IgdbService) +โ””โ”€โ”€ jobs/ # Background jobs (IgdbSyncJob) config/ -โ”œโ”€โ”€ routes.rb # URL routing -โ”œโ”€โ”€ database.yml # Database configuration -โ””โ”€โ”€ environments/ # Environment-specific config +โ”œโ”€โ”€ routes.rb # Routes +โ”œโ”€โ”€ database.yml # Database config +โ”œโ”€โ”€ queue.yml # Solid Queue config +โ””โ”€โ”€ recurring.yml # Recurring jobs (IGDB sync) -db/ -โ”œโ”€โ”€ migrate/ # Database migrations -โ”œโ”€โ”€ seeds.rb # Seed data -โ””โ”€โ”€ schema.rb # Current database schema - -test/ -โ”œโ”€โ”€ models/ # Model tests -โ”œโ”€โ”€ controllers/ # Controller tests -โ”œโ”€โ”€ system/ # End-to-end tests -โ””โ”€โ”€ fixtures/ # Test data -``` - -## Common Tasks - -### Adding a New Model - -1. Generate the model: -```bash -rails generate model Post user:references title:string body:text published:boolean -``` - -2. Edit the migration (add indexes, constraints, RLS if user-scoped): -```ruby -class CreatePosts < ActiveRecord::Migration[8.1] - def change - create_table :posts do |t| - t.references :user, null: false, foreign_key: true, index: true - t.string :title, null: false - t.text :body - t.boolean :published, default: false, null: false - t.timestamps - end - - add_index :posts, :title - - # Enable RLS if user-scoped - execute <<-SQL - ALTER TABLE posts ENABLE ROW LEVEL SECURITY; - CREATE POLICY posts_isolation_policy ON posts - USING (user_id = current_setting('app.current_user_id', true)::bigint); - SQL - end -end -``` - -3. Run the migration: -```bash -rails db:migrate -``` - -4. Add associations and validations to the model: -```ruby -class Post < ApplicationRecord - belongs_to :user - - validates :title, presence: true - validates :body, presence: true - - scope :published, -> { where(published: true) } -end -``` - -5. Add association to User model: -```ruby -class User < ApplicationRecord - has_many :posts, dependent: :destroy -end -``` - -### Adding a New Controller - -1. Generate the controller: -```bash -rails generate controller Posts index show new create edit update destroy -``` - -2. Implement controller actions: -```ruby -class PostsController < ApplicationController - before_action :require_authentication - before_action :set_post, only: [:show, :edit, :update, :destroy] - - def index - @posts = current_user.posts.order(created_at: :desc) - end - - def show - end - - def new - @post = current_user.posts.build - end - - def create - @post = current_user.posts.build(post_params) - - if @post.save - redirect_to @post, notice: "Post created successfully." - else - render :new, status: :unprocessable_entity - end - end - - def edit - end - - def update - if @post.update(post_params) - redirect_to @post, notice: "Post updated successfully." - else - render :edit, status: :unprocessable_entity - end - end - - def destroy - @post.destroy - redirect_to posts_path, notice: "Post deleted successfully." - end - - private - - def set_post - @post = current_user.posts.find(params[:id]) - end - - def post_params - params.require(:post).permit(:title, :body, :published) - end -end -``` - -3. Add routes: -```ruby -resources :posts -``` - -4. Create views in `app/views/posts/` - -### Adding an API Endpoint - -1. Create API controller: -```bash -mkdir -p app/controllers/api/v1 -``` - -2. Create controller file: -```ruby -# app/controllers/api/v1/posts_controller.rb -module Api - module V1 - class PostsController < BaseController - def index - @posts = current_user.posts.order(created_at: :desc) - render json: @posts - end - - def show - @post = current_user.posts.find(params[:id]) - render json: @post - end - - def create - @post = current_user.posts.build(post_params) - - if @post.save - render json: @post, status: :created - else - render json: { errors: @post.errors.full_messages }, - status: :unprocessable_entity - end - end - - private - - def post_params - params.require(:post).permit(:title, :body, :published) - end - end - end -end -``` - -3. Add API routes: -```ruby -namespace :api do - namespace :v1 do - resources :posts, only: [:index, :show, :create] - end -end -``` - -### Adding a Background Job - -1. Generate the job: -```bash -rails generate job ProcessData -``` - -2. Implement the job: -```ruby -class ProcessDataJob < ApplicationJob - queue_as :default - - def perform(user_id) - user = User.find(user_id) - # Do some processing - end -end -``` - -3. Enqueue the job: -```ruby -ProcessDataJob.perform_later(user.id) -``` - -### Adding Email Functionality - -1. Generate a mailer: -```bash -rails generate mailer UserMailer welcome -``` - -2. Implement the mailer: -```ruby -class UserMailer < ApplicationMailer - def welcome(user) - @user = user - mail(to: @user.email, subject: "Welcome to TurboVault!") - end -end -``` - -3. Create email templates in `app/views/user_mailer/` - -4. Configure SMTP in `config/environments/`: -```ruby -config.action_mailer.delivery_method = :smtp -config.action_mailer.smtp_settings = { - address: ENV['SMTP_ADDRESS'], - port: ENV['SMTP_PORT'], - user_name: ENV['SMTP_USERNAME'], - password: ENV['SMTP_PASSWORD'], - authentication: 'plain', - enable_starttls_auto: true -} -``` - -5. Send the email: -```ruby -UserMailer.welcome(user).deliver_later -``` - -## Testing Guide - -### Writing Model Tests - -```ruby -# test/models/game_test.rb -require "test_helper" - -class GameTest < ActiveSupport::TestCase - test "should not save game without title" do - game = Game.new - assert_not game.save, "Saved game without title" - end - - test "should save valid game" do - game = games(:one) # Uses fixture - assert game.save, "Failed to save valid game" - end - - test "should belong to user" do - game = games(:one) - assert_respond_to game, :user - end -end -``` - -### Writing Controller Tests - -```ruby -# test/controllers/games_controller_test.rb -require "test_helper" - -class GamesControllerTest < ActionDispatch::IntegrationTest - setup do - @user = users(:one) - sign_in_as @user # Helper method to sign in - @game = games(:one) - end - - test "should get index" do - get games_url - assert_response :success - end - - test "should create game" do - assert_difference('Game.count') do - post games_url, params: { - game: { - title: "New Game", - platform_id: platforms(:one).id, - format: "physical" - } - } - end - - assert_redirected_to game_path(Game.last) - end -end -``` - -### Writing System Tests - -```ruby -# test/system/games_test.rb -require "application_system_test_case" - -class GamesTest < ApplicationSystemTestCase - setup do - @user = users(:one) - sign_in_as @user - end - - test "visiting the index" do - visit games_url - assert_selector "h1", text: "My Games" - end - - test "creating a game" do - visit games_url - click_on "Add Game" - - fill_in "Title", with: "Test Game" - select "Nintendo 64", from: "Platform" - select "Physical", from: "Format" - - click_on "Create Game" - - assert_text "Game was successfully created" - end -end -``` - -## Debugging - -### Rails Console - -```bash -rails console - -# Test queries -User.first -Game.where(format: :physical).count -current_user.games.includes(:platform) - -# Test RLS -ActiveRecord::Base.connection.execute( - "SET LOCAL app.current_user_id = 1" -) -``` - -### Logs - -```bash -# Tail development log -tail -f log/development.log - -# View specific log -cat log/test.log -``` - -### Debug with Byebug - -Add to your code: -```ruby -require 'debug' -debugger # Execution will pause here -``` - -Then interact in the terminal: -``` -n # Next line -c # Continue -p var # Print variable -exit # Exit debugger -``` - -## Performance Tips - -### Avoid N+1 Queries - -Bad: -```ruby -@games = current_user.games -# In view: @games.each { |g| g.platform.name } # N+1! -``` - -Good: -```ruby -@games = current_user.games.includes(:platform) -``` - -### Use Database Indexes - -```ruby -add_index :games, :title -add_index :games, [:user_id, :platform_id] -``` - -### Use Counter Caches - -```ruby -class Collection < ApplicationRecord - has_many :games, counter_cache: true -end -``` - -### Pagination - -```ruby -@games = current_user.games.page(params[:page]).per(25) +k8s/ # Kubernetes manifests +docs/ # Documentation ``` ## Environment Variables -Create `.env` for development (never commit!): -``` -DATABASE_HOST=localhost -DATABASE_USERNAME=postgres -DATABASE_PASSWORD=postgres -SMTP_ADDRESS=smtp.example.com -SMTP_USERNAME=user@example.com -SMTP_PASSWORD=secret -``` - -Load with: -```ruby -# config/application.rb -config.before_configuration do - env_file = Rails.root.join('.env') - if File.exist?(env_file) - File.readlines(env_file).each do |line| - key, value = line.split('=', 2) - ENV[key.strip] = value.strip if key && value - end - end -end -``` - -## Deployment - -### Kamal (Recommended) - -Already configured! Just: +Copy `.env.example` to `.env`: ```bash -# First time setup -kamal setup - -# Deploy -kamal deploy - -# Check status -kamal app exec --interactive --reuse "bin/rails console" +# Optional: IGDB API +IGDB_CLIENT_ID=your_client_id +IGDB_CLIENT_SECRET=your_client_secret ``` -### Railway/Render +Get credentials: https://dev.twitch.tv -1. Push to Git -2. Connect repository -3. Set environment variables -4. Add build command: `bundle install && rails db:migrate` -5. Add start command: `rails server -b 0.0.0.0` +## Contributing + +1. Fork repository +2. Create feature branch +3. Make changes +4. Run: `task test && task lint:fix && task typecheck` +5. Submit PR ## Troubleshooting -### Database Connection Errors - ```bash -# Check if PostgreSQL is running -docker compose ps +# Database issues +task docker:up && task db:reset -# Start PostgreSQL -task docker:up +# Type errors +task tapioca:all && task typecheck -# Check database configuration -cat config/database.yml +# Tests failing +task db:test:prepare && task test ``` - -### Asset Issues - -```bash -# Rebuild assets -rails assets:precompile - -# Rebuild Tailwind -rails tailwindcss:build -``` - -### Migration Issues - -```bash -# Check migration status -rails db:migrate:status - -# Rollback and retry -rails db:rollback -rails db:migrate -``` - -## Resources - -- [Rails Guides](https://guides.rubyonrails.org/) -- [Rails API Documentation](https://api.rubyonrails.org/) -- [Tailwind CSS Docs](https://tailwindcss.com/docs) -- [Hotwire Documentation](https://hotwired.dev/) -- [PostgreSQL Documentation](https://www.postgresql.org/docs/) - -## Getting Help - -1. Check the logs: `tail -f log/development.log` -2. Use Rails console: `rails console` -3. Check database: `rails dbconsole` -4. Read the error message carefully -5. Search Stack Overflow -6. Check Rails Guides - -## Best Practices - -1. **Always filter by current_user** in controllers -2. **Use strong parameters** for mass assignment -3. **Add validations** to models -4. **Write tests** for new features -5. **Keep controllers thin** - move logic to models -6. **Use concerns** for shared code -7. **Keep views simple** - use helpers for complex logic -8. **Add indexes** for frequently queried columns -9. **Use scopes** for common queries -10. **Document your API** endpoints - -Happy coding! ๐Ÿš€ diff --git a/docs/IGDB_INTEGRATION.md b/docs/IGDB_INTEGRATION.md index 4f46114..fa0b06f 100644 --- a/docs/IGDB_INTEGRATION.md +++ b/docs/IGDB_INTEGRATION.md @@ -1,391 +1,114 @@ -# IGDB Integration Guide +# IGDB Integration -## Overview - -TurboVault now integrates with IGDB (Internet Game Database) to automatically match your games with their database entries. This provides access to cover art, metadata, and consistent game identification across users. +TurboVault integrates with the [Internet Game Database (IGDB)](https://www.igdb.com/) to automatically enrich your games with metadata, cover art, and genre information. ## How It Works -### 1. **User Opt-In** -- Users must enable IGDB sync in Settings -- Default: OFF (privacy by design) -- Can be toggled on/off anytime +1. **Background sync** runs every 30 minutes (configurable) +2. **Searches IGDB** for games without metadata +3. **Creates suggestions** with confidence scores (0-100%) +4. **Users review** and approve/reject matches +5. **Metadata applied** when approved (cover art, genres, release dates) -### 2. **Automatic Sync Job** -- Runs every 30 minutes -- Only processes users with sync enabled -- Matches games that don't have IGDB IDs yet +## Setup -### 3. **Smart Matching** -- Searches IGDB using game title + platform -- Returns top 3 matches with confidence scores -- Uses fuzzy matching and platform filtering +### Get IGDB Credentials -### 4. **User Review** -- Users review suggested matches -- See cover art, release year, platform -- Approve or reject each match -- Even high-confidence matches require approval (prevents errors) +1. Create account at https://dev.twitch.tv +2. Register an application +3. Copy Client ID and Client Secret -### 5. **Public Cache** -- Matched games stored in `igdb_games` table -- Shared across all users (public data only) -- Reduces API calls for common games +### Configure -## Features +Add to `.env`: -### โœ… What's Implemented - -1. **User Settings** - - Enable/disable IGDB sync - - Last sync timestamp - -2. **Background Job** - - Automatic recurring sync (every 30 minutes) - - Rate limiting (4 req/sec) - - Progress tracking - - Error handling - -3. **Smart Matching** - - Title similarity scoring - - Platform matching - - Confidence calculation (0-100%) - - Top 3 results per game - -4. **Review Interface** - - See all pending matches - - View cover images - - Approve/reject matches - - Bulk reject option - -5. **Public Game Cache** - - Shared IGDB data - - Cover URLs - - Release dates - - Match popularity tracking - -6. **Platform Mappings** - - 26 platforms mapped to IGDB IDs - - Easy to extend - -## Database Schema - -### New Tables - -**igdb_games** - Public cache of IGDB game data -- igdb_id (unique) -- name, slug, cover_url -- summary, first_release_date -- match_count (popularity) - -**igdb_platform_mappings** - Maps our platforms to IGDB -- platform_id โ†’ igdb_platform_id -- Pre-seeded with 26 common platforms - -**igdb_match_suggestions** - Pending matches for review -- game_id + igdb_id -- confidence_score -- status (pending/approved/rejected) -- Cover and metadata for review - -### New Columns - -**users** -- igdb_sync_enabled (boolean) -- igdb_last_synced_at (timestamp) - -**games** -- igdb_matched_at (timestamp) -- igdb_match_status (string) -- igdb_match_confidence (decimal) - -## API Credentials - -**Backend Only - Never Exposed to Frontend** - -Set in `.env` (gitignored): ```bash -IGDB_CLIENT_ID=your_client_id -IGDB_ACCESS_TOKEN=your_token +IGDB_CLIENT_ID=your_client_id_here +IGDB_CLIENT_SECRET=your_client_secret_here ``` -Get credentials from: https://api-docs.igdb.com/ +### Enable for Users -Current credentials are already set in the `.env` file. +New users have IGDB sync enabled by default. Users can opt-out in Settings. ## Usage -### For Users +### Review Matches -1. **Enable Sync** - - Go to Settings - - Check "Enable IGDB game matching" - - Click "Update Profile" +Visit `/igdb_matches` to see pending suggestions. -2. **Trigger Sync** - - Go to IGDB Matches page - - Click "Sync Now" - - Wait a few minutes +Each suggestion shows: +- Game title +- Cover art +- Platform +- Confidence score +- Genres from IGDB -3. **Review Matches** - - View pending matches - - See confidence scores - - See cover art and release year - - Approve correct matches - - Reject incorrect ones +**Actions:** +- โœ… **Approve** - Apply metadata to your game +- โŒ **Reject** - Dismiss suggestion +- ๐Ÿ”„ **Sync Now** - Manually trigger sync for your games -4. **Check Progress** - - View stats: Matched, Unmatched, Pending Review - - See last sync time - - Badge in navigation shows pending count +### Manual Commands -### For Developers - -**Manually Trigger Sync:** -```ruby -IgdbSyncJob.perform_later -``` - -**Search IGDB Directly:** -```ruby -service = IgdbService.new -results = service.search_game("Ocarina of Time", platform) -``` - -**Check Platform Mappings:** -```ruby -IgdbPlatformMapping.igdb_id_for_platform(Platform.find_by(name: "Nintendo 64")) -# => 4 -``` - -**Seed More Platform Mappings:** -```ruby -IgdbPlatformMapping.seed_common_mappings! -``` - -## Match Confidence Scoring - -Confidence score is 0-100%: - -**Title Matching (0-70 points)** -- Exact match: 70 points -- Contains match: 50 points -- Word overlap: 0-40 points - -**Platform Matching (0-30 points)** -- Exact platform: 30 points -- Similar platform: 20 points -- No match: 0 points - -**Result Categories:** -- 95-100%: Very High (auto-suggest first) -- 70-94%: High Confidence -- 50-69%: Medium Confidence -- 0-49%: Low Confidence - -## Rate Limiting - -**IGDB Limits:** 4 requests/second - -**Our Implementation:** -- 0.3s sleep between requests -- 1s sleep every 10 games -- Handles 429 errors gracefully -- Job can be safely interrupted - -## Job Scheduling - -**Recurring Schedule:** -- Every 30 minutes -- Configured in `config/queue.yml` -- Uses Solid Queue - -**Single Instance:** -- Uses Rails cache lock -- Prevents multiple instances running -- 2-hour timeout (auto-releases) - -**Manual Trigger:** -- "Sync Now" button in UI -- Or: `IgdbSyncJob.perform_later` - -## Troubleshooting - -### No Matches Found - -**Possible Reasons:** -1. Game title too different from IGDB -2. Platform not mapped -3. Game not in IGDB database - -**Solutions:** -- Check game title spelling -- Try alternate titles -- Check if platform is mapped -- Some games may not be in IGDB - -### Rate Limit Errors - -If you see 429 errors: -- Job will automatically pause -- Will resume on next run -- Check IGDB API status - -### Job Not Running - -**Check:** -```ruby -# Is job scheduled? -SolidQueue::RecurringTask.all - -# Is job running? -IgdbSyncJob.running? - -# Clear lock if stuck -Rails.cache.delete("igdb_sync_job:running") -``` - -### Matches Not Appearing - -**Check:** -1. Is sync enabled for user? -2. Are there unmatched games? -3. Check logs: `tail -f log/development.log` -4. Run manually: `IgdbSyncJob.perform_now` - -## Adding New Platform Mappings - -```ruby -# Find IGDB platform ID from: https://api-docs.igdb.com/#platform -platform = Platform.find_by(name: "Your Platform") -IgdbPlatformMapping.create!( - platform: platform, - igdb_platform_id: 123, # IGDB ID - igdb_platform_name: "Platform Name" -) -``` - -## Cover Images - -**IGDB CDN URLs:** -```ruby -# In model: -igdb_game.cover_image_url("cover_big") -# => https://images.igdb.com/igdb/image/upload/t_cover_big/abc123.jpg - -# Available sizes: -# - cover_small (90x128) -# - cover_big (264x374) -# - screenshot_med (569x320) -# - screenshot_big (1280x720) -# - screenshot_huge (1920x1080) -# - 720p, 1080p -``` - -## Privacy & Security - -**What's Shared:** -- Only IGDB game data (names, covers, dates) -- NO user-specific data (prices, locations, notes) -- `igdb_games` table is public metadata only - -**What's Private:** -- User's game ownership -- Collections -- Personal notes, prices, locations -- Which user matched which game - -**API Credentials:** -- Stored in ENV variables -- Never sent to frontend -- Backend-only service class - -## Performance - -**Typical Sync:** -- ~100 games: 2-3 minutes -- ~500 games: 10-15 minutes -- Rate limited for API safety - -**Database:** -- Indexes on all foreign keys -- Cache lookup before API calls -- Efficient batch processing - -## Future Enhancements - -**Phase 2 Ideas:** -- Auto-approve very high confidence (>95%) -- Bulk approve/reject -- Search IGDB directly from Add Game form -- Download and store cover images locally -- More metadata (genres, ratings, descriptions) -- Manual IGDB search for failed matches -- Game recommendations based on IGDB data - -## API Documentation - -**IGDB API Docs:** https://api-docs.igdb.com/ - -**Authentication:** -- Requires Twitch Client ID + Access Token -- Token must be refreshed periodically (check expiry) - -**Endpoints Used:** -- `POST /v4/games` - Search and fetch game data - -**Query Example:** -``` -search "Zelda Ocarina"; -fields id, name, slug, cover.url, summary, first_release_date, platforms.name; -where platforms = (4); -limit 3; -``` - -## Testing - -**Test the Flow:** -1. Enable sync in settings -2. Add a game without IGDB match -3. Click "Sync Now" -4. Wait 1-2 minutes -5. Refresh page - should see matches -6. Approve or reject matches - -**Test Games:** -- "The Legend of Zelda: Ocarina of Time" (N64) - Should find match -- "Super Mario 64" (N64) - Should find match -- "Made Up Game Name" - Should find no results - -## Support - -**Check Logs:** ```bash -tail -f log/development.log | grep IGDB -``` +# Trigger sync manually +task igdb:sync -**Rails Console:** -```ruby # Check sync status -User.find_by(email: "demo@turbovault.com").igdb_sync_enabled +task igdb:status -# View pending matches -User.first.igdb_match_suggestions.status_pending - -# Test IGDB service -service = IgdbService.new -results = service.search_game("Mario 64", Platform.find_by(abbreviation: "N64")) +# Clear stuck jobs +task igdb:clear ``` -## Summary +## How Matching Works -The IGDB integration is now fully functional: -- โœ… User opt-in settings -- โœ… Automatic sync every 30 minutes -- โœ… Smart matching with confidence scores -- โœ… Review UI with cover art -- โœ… Public game cache -- โœ… Rate limiting and error handling -- โœ… Privacy-preserving design +**Confidence scoring:** +- Title match: 0-70 points +- Platform match: 0-30 points +- 100% = exact title + platform match -Users can now match their games with IGDB for better organization and future features like cover art display! +**Fallback search:** +- First tries with platform filter +- Falls back to no platform if no results + +**Genre mapping:** +IGDB genres are automatically mapped to your local genres (e.g., "Role-playing (RPG)" โ†’ "RPG"). + +## Platform Support + +26 platforms supported including: +- Nintendo (NES, SNES, N64, GameCube, Wii, Switch) +- PlayStation (PS1-PS5, PSP, Vita) +- Xbox (Xbox, 360, One, Series X/S) +- Sega (Genesis, Dreamcast, Saturn) +- PC platforms (Steam, Epic, GOG) + +## Background Job + +**Schedule:** Every 30 minutes +**Configuration:** `config/queue.yml` +**Job:** `IgdbSyncJob` + +The job: +- Only processes users with `igdb_sync_enabled: true` +- Skips games with `igdb_id` already set +- Creates up to 3 match suggestions per game +- Respects rate limits (4 requests/second) + +## API Rate Limits + +- **Limit:** 4 requests per second +- **Handled:** Automatic 0.3s delay between requests +- **Caching:** Games cached in `igdb_games` table + +## Disabling IGDB + +Users can disable in Settings โ†’ "Enable IGDB metadata sync" + +Admins can disable globally by removing env vars or stopping the recurring job. + +## Attribution + +IGDB attribution link in footer (required by IGDB terms). diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md index 2d2eeae..81ccd28 100644 --- a/docs/QUICK_START.md +++ b/docs/QUICK_START.md @@ -1,305 +1,50 @@ -# ๐Ÿš€ TurboVault Quick Start Guide +# Quick Start Guide -Get TurboVault deployed to Kubernetes in minutes! +Get TurboVault running locally in minutes. -## Prerequisites +## Local Development -- GitHub account -- Kubernetes cluster (k3s, minikube, EKS, GKE, etc.) -- kubectl configured -- PostgreSQL database (or use in-cluster Helm chart) +### Prerequisites -## Step 1: Push to GitHub +- Ruby 3.3+ +- Docker (for services) + +### Setup ```bash -cd turbovault-web +# Clone +git clone https://github.com/ryankazokas/turbovault-app.git +cd turbovault -# Initialize git -git init -git add . -git commit -m "Initial commit: TurboVault" +# Start services (PostgreSQL + Mailpit) +task docker:up -# Add your GitHub remote -git remote add origin https://github.com/YOUR_USERNAME/turbovault.git -git push -u origin main +# Setup database +task db:setup + +# Start server +task dev ``` -## Step 2: Tag a Release +Visit **http://localhost:3000** -```bash -git tag v1.0.0 -git push origin v1.0.0 -``` +### Demo Account -**What happens:** -- GitHub Actions automatically triggers -- Builds Docker image -- Pushes to GitHub Container Registry (ghcr.io) -- Image: `ghcr.io/YOUR_USERNAME/turbovault:v1.0.0` +- Email: `demo@turbovault.com` +- Password: `password123` -**Check progress:** GitHub โ†’ Actions tab +## Optional: IGDB Integration -## Step 3: Prepare Kubernetes Secrets +For automatic game metadata: -```bash -# Copy the template -cp k8s/secrets.yaml.example k8s/secrets.yaml +1. Create app at https://dev.twitch.tv +2. Add to `.env`: + ```bash + IGDB_CLIENT_ID=your_id + IGDB_CLIENT_SECRET=your_secret + ``` +3. Restart server -# Generate Rails secret -rails secret -# Copy output +## Deployment -# Edit secrets.yaml -nano k8s/secrets.yaml -``` - -Add your values: -- `SECRET_KEY_BASE` - from `rails secret` command -- `DATABASE_PASSWORD` - your PostgreSQL password -- `IGDB_CLIENT_ID` (optional) - from https://dev.twitch.tv -- `IGDB_CLIENT_SECRET` (optional) - from Twitch developer portal - -**Important:** Do NOT commit `k8s/secrets.yaml` (it's gitignored) - -## Step 4: Update Kubernetes Manifests - -Edit `k8s/deployment.yaml` and `k8s/migrate-job.yaml`: - -```yaml -# Change this line: -image: ghcr.io/username/turbovault:latest - -# To your actual GitHub username: -image: ghcr.io/YOUR_USERNAME/turbovault:v1.0.0 -``` - -Edit `k8s/configmap.yaml`: - -```yaml -DATABASE_HOST: "your-postgres-host" # e.g., postgres-service or external host -DATABASE_NAME: "turbovault_production" -DATABASE_USERNAME: "turbovault" -``` - -Edit `k8s/ingress.yaml` (optional): - -```yaml -# Change to your domain -host: turbovault.yourdomain.com -``` - -Or skip ingress and use port-forwarding for testing. - -## Step 5: Deploy to Kubernetes - -### Option A: Automated Script - -```bash -./scripts/deploy-k8s.sh -``` - -Follow the prompts: -- Enter registry: `ghcr.io/YOUR_USERNAME` -- Is it private? `n` (if image is public) or `y` (if private) -- Deployment starts! - -### Option B: Manual - -```bash -# Apply manifests -kubectl apply -f k8s/namespace.yaml -kubectl apply -f k8s/configmap.yaml -kubectl apply -f k8s/secrets.yaml - -# Run database migration -kubectl apply -f k8s/migrate-job.yaml -kubectl wait --for=condition=complete --timeout=300s job/turbovault-migrate -n turbovault - -# Deploy application -kubectl apply -f k8s/deployment.yaml -kubectl apply -f k8s/service.yaml -kubectl apply -f k8s/ingress.yaml # optional -``` - -## Step 6: Verify Deployment - -```bash -# Check pods -kubectl get pods -n turbovault - -# Should show: -# NAME READY STATUS RESTARTS AGE -# turbovault-xxxxxxxxxx-xxxxx 1/1 Running 0 30s -# turbovault-xxxxxxxxxx-xxxxx 1/1 Running 0 30s - -# Check logs -kubectl logs -f deployment/turbovault -n turbovault -``` - -## Step 7: Access the Application - -### Option A: Via Ingress (if configured) - -Visit: `https://turbovault.yourdomain.com` - -### Option B: Port Forward (for testing) - -```bash -kubectl port-forward svc/turbovault-service 3000:80 -n turbovault -``` - -Visit: `http://localhost:3000` - -### Option C: LoadBalancer (cloud) - -```bash -kubectl get svc turbovault-service -n turbovault - -# Get EXTERNAL-IP and visit that IP -``` - -## Step 8: Create Admin Account - -1. Visit the application -2. Click **Sign Up** -3. Create your account -4. Start adding games! - -## ๐ŸŽ‰ You're Done! - -TurboVault is now running on Kubernetes! - -## Next Steps - -### Make it Public - -Want others to access your package? - -1. Go to GitHub โ†’ Your Profile โ†’ Packages -2. Find `turbovault` package -3. Package Settings โ†’ Change Visibility โ†’ Public - -### Keep it Private - -By default, GitHub Container Registry packages are private. Only you can pull the image. Perfect for personal deployments! - -For Kubernetes to pull private images: - -```bash -kubectl create secret docker-registry ghcr-secret \ - --docker-server=ghcr.io \ - --docker-username=YOUR_USERNAME \ - --docker-password=YOUR_GITHUB_TOKEN \ - --namespace=turbovault -``` - -Then uncomment in `k8s/deployment.yaml`: -```yaml -imagePullSecrets: -- name: ghcr-secret -``` - -### Add SSL/TLS - -Install cert-manager and configure Let's Encrypt: - -See [DEPLOYMENT.md](DEPLOYMENT.md) for full instructions. - -### Update the App - -When you want to deploy a new version: - -```bash -# Make changes -git add . -git commit -m "Add new feature" -git push origin main - -# Create new tag -git tag v1.1.0 -git push origin v1.1.0 - -# Wait for GitHub Actions to build - -# Update deployment -kubectl set image deployment/turbovault \ - turbovault=ghcr.io/YOUR_USERNAME/turbovault:v1.1.0 \ - -n turbovault - -# Watch rollout -kubectl rollout status deployment/turbovault -n turbovault -``` - -## Troubleshooting - -### Pods not starting - -```bash -kubectl describe pod -l app=turbovault -n turbovault -``` - -Common issues: -- Image pull error โ†’ Check image path in deployment.yaml -- Database connection โ†’ Check secrets.yaml and configmap.yaml -- Crash loop โ†’ Check logs: `kubectl logs -l app=turbovault -n turbovault` - -### Can't access application - -```bash -# Check service -kubectl get svc -n turbovault - -# Check ingress (if using) -kubectl get ingress -n turbovault - -# Try port-forward to test -kubectl port-forward svc/turbovault-service 3000:80 -n turbovault -``` - -### Build failed - -Go to GitHub โ†’ Actions โ†’ Click on failed workflow - -Common issues: -- Dockerfile error โ†’ Fix and push again -- Permission denied โ†’ Check workflow has `packages: write` permission - -## Database Setup - -### Option 1: External PostgreSQL (Recommended) - -Use a managed database (RDS, Cloud SQL, etc.) or existing PostgreSQL server. - -Update `k8s/configmap.yaml` with connection details. - -### Option 2: In-Cluster PostgreSQL - -```bash -helm repo add bitnami https://charts.bitnami.com/bitnami -helm install postgres bitnami/postgresql \ - --namespace turbovault \ - --set auth.database=turbovault_production \ - --set auth.username=turbovault \ - --set auth.password=changeme - -# Update k8s/configmap.yaml: -# DATABASE_HOST: postgres-postgresql -``` - -## Complete Documentation - -- [Full Deployment Guide](DEPLOYMENT.md) - Detailed deployment instructions -- [Development Guide](DEVELOPMENT_GUIDE.md) - Local development setup -- [API Documentation](API_DOCUMENTATION.md) - RESTful API reference -- [IGDB Integration](IGDB_INTEGRATION.md) - Game metadata matching - -## Support - -Need help? -- ๐Ÿ“– Check the [docs/](.) folder -- ๐Ÿ› [Open an issue](https://github.com/yourusername/turbovault/issues) -- ๐Ÿ’ฌ [Discussions](https://github.com/yourusername/turbovault/discussions) - ---- - -**Congratulations!** You've successfully deployed TurboVault to Kubernetes! ๐ŸŽ‰ +For production deployment to Kubernetes, see [Deployment Guide](DEPLOYMENT.md). diff --git a/docs/README.md b/docs/README.md index 554eaec..4741eb0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,45 +1,14 @@ # TurboVault Documentation -Complete documentation for TurboVault - Video Game Collection Tracker +## Quick Links -## ๐Ÿ“– Documentation +- [Quick Start](QUICK_START.md) - Get up and running +- [Development](DEVELOPMENT_GUIDE.md) - Local development +- [Deployment](DEPLOYMENT.md) - Kubernetes deployment +- [API Reference](API_DOCUMENTATION.md) - RESTful API +- [IGDB Integration](IGDB_INTEGRATION.md) - Automatic metadata -### Getting Started -- [Main README](../README.md) - Project overview -- โญ [Quick Start Guide](QUICK_START.md) - **Deploy in minutes!** -- [Demo Account](DEMO_ACCOUNT.md) - Try the demo +## Demo -### Deployment & Development -- [Deployment Guide](DEPLOYMENT.md) - Complete deployment reference -- [Development Guide](DEVELOPMENT_GUIDE.md) - Local development & contributing -- [CI Pipeline](CI_PIPELINE.md) - GitHub Actions & quality checks -- [Kubernetes README](../k8s/README.md) - Kubernetes deployment - -### Features & Development -- [API Documentation](API_DOCUMENTATION.md) - RESTful API reference -- [IGDB Integration](IGDB_INTEGRATION.md) - Game metadata matching -- [Themes](THEMES.md) - Theme customization -- [Sorbet Type Checking](SORBET.md) - Gradual static types - -### Configuration -- [GitHub Secrets Setup](../.github/SECRETS_SETUP.md) - Optional custom registry -- [What to Commit](../.github/WHAT_TO_COMMIT.md) - Safe for open source - ---- - -## ๐Ÿš€ Quick Navigation - -**New to TurboVault?** -1. Read [Main README](../README.md) -2. Deploy with [Quick Start Guide](QUICK_START.md) โญ -3. Or develop locally with [Development Guide](DEVELOPMENT_GUIDE.md) - -**Looking for something specific?** -- Deploying to production โ†’ [DEPLOYMENT.md](DEPLOYMENT.md) -- Using the API โ†’ [API_DOCUMENTATION.md](API_DOCUMENTATION.md) -- Game metadata features โ†’ [IGDB_INTEGRATION.md](IGDB_INTEGRATION.md) -- Customizing themes โ†’ [THEMES.md](THEMES.md) - ---- - -**Need help?** Check the [Main README](../README.md) or [open an issue](https://github.com/yourusername/turbovault/issues). +Email: `demo@turbovault.com` +Password: `password123` diff --git a/docs/SORBET.md b/docs/SORBET.md deleted file mode 100644 index ced5538..0000000 --- a/docs/SORBET.md +++ /dev/null @@ -1,520 +0,0 @@ -# Sorbet Type Checking Guide - -TurboVault uses [Sorbet](https://sorbet.org/) for gradual static type checking. - -## Quick Start - -### First Time Setup - -After installing gems, initialize Sorbet: - -```bash -# Install dependencies -bundle install - -# Initialize Tapioca (generates type definitions) -task tapioca:init - -# Generate RBI files for gems and Rails DSLs -task tapioca:all -``` - -This creates: -- `sorbet/` - Sorbet configuration -- `sorbet/rbi/gems/` - Type definitions for gems (gitignored) -- `sorbet/rbi/dsl/` - Generated types for Rails models, etc. (gitignored) - -### Running Type Checks - -```bash -# Check all files -task typecheck - -# Watch mode (re-checks on file changes) -task typecheck:watch - -# Or use Sorbet directly -bundle exec srb tc -``` - -## Type Strictness Levels - -Sorbet uses gradual typing with 5 strictness levels: - -### `# typed: false` (Default - No Checking) -```ruby -# typed: false -class MyClass - def do_something(x) - x + 1 # No type checking at all - end -end -``` -- **Use for:** Legacy code, external gems, code you're not ready to type yet -- **Checking:** None - -### `# typed: true` (Recommended) -```ruby -# typed: true -class User < ApplicationRecord - extend T::Sig - - sig { returns(String) } - def theme_class - "theme-#{theme}" - end -end -``` -- **Use for:** Most application code -- **Checking:** Method signatures you define with `sig` - -### `# typed: strict` (Advanced) -- **Use for:** Critical business logic, libraries -- **Checking:** All method signatures required, no untyped code - -### `# typed: strong` (Expert) -- **Use for:** Maximum safety -- **Checking:** No `T.untyped`, no runtime checks - -## Writing Type Signatures - -### Basic Method Signatures - -```ruby -# typed: true -class Game < ApplicationRecord - extend T::Sig - - # No parameters, returns String - sig { returns(String) } - def title - read_attribute(:title) - end - - # One parameter, returns Boolean - sig { params(user: User).returns(T::Boolean) } - def owned_by?(user) - self.user_id == user.id - end - - # Multiple parameters - sig { params(title: String, platform: Platform).returns(Game) } - def self.create_game(title, platform) - create(title: title, platform: platform) - end - - # Void (returns nothing meaningful) - sig { void } - def update_cache - Rails.cache.write("game_#{id}", self) - end -end -``` - -### Nilable Types - -```ruby -# typed: true -class Game < ApplicationRecord - extend T::Sig - - # Can return Platform or nil - sig { returns(T.nilable(Platform)) } - def platform - super - end - - # Parameter can be nil - sig { params(platform_id: T.nilable(Integer)).returns(T.untyped) } - def self.by_platform(platform_id) - where(platform_id: platform_id) if platform_id - end -end -``` - -### Arrays and Hashes - -```ruby -# typed: true -class IgdbService - extend T::Sig - - # Array of Hashes - sig { params(title: String).returns(T::Array[T::Hash[Symbol, T.untyped]]) } - def search_game(title) - # Returns: [{id: 1, name: "Game"}, {id: 2, name: "Game 2"}] - end - - # Array of specific type - sig { returns(T::Array[Game]) } - def all_games - Game.all.to_a - end - - # Hash with specific keys - sig { returns(T::Hash[String, Integer]) } - def game_counts - { "total" => 100, "completed" => 50 } - end -end -``` - -### Optional Parameters - -```ruby -# typed: true -class IgdbService - extend T::Sig - - # Optional parameter with default - sig { params(title: String, limit: Integer).returns(T::Array[T.untyped]) } - def search_game(title, limit = 3) - # ... - end - - # Optional keyword parameter - sig { params(title: String, platform: T.nilable(String)).returns(T::Array[T.untyped]) } - def search(title, platform: nil) - # ... - end -end -``` - -### Instance Variables - -```ruby -# typed: true -class IgdbService - extend T::Sig - - sig { void } - def initialize - @client_id = T.let(ENV.fetch("IGDB_CLIENT_ID"), String) - @client_secret = T.let(ENV.fetch("IGDB_CLIENT_SECRET"), String) - @access_token = T.let(get_or_refresh_token, String) - end -end -``` - -`T.let(value, Type)` declares the type of an instance variable. - -### Untyped Code - -When you can't determine the type (ActiveRecord relations, dynamic code): - -```ruby -# typed: true -class GamesController < ApplicationController - extend T::Sig - - sig { void } - def index - # ActiveRecord relations are complex, use T.untyped - @games = T.let(current_user.games.includes(:platform), T.untyped) - end -end -``` - -Use `T.untyped` sparingly - it bypasses type checking. - -### CSV Rows (Common Pattern) - -CSV::Row acts like a Hash but Sorbet doesn't know that: - -```ruby -# typed: true -sig { void } -def bulk_create - csv = CSV.parse(csv_text, headers: true) - - csv.each_with_index do |row, index| - # Tell Sorbet row is untyped to avoid Array vs Hash confusion - row = T.let(row, T.untyped) - - # Now you can access row["column"] without errors - title = row["title"] - platform = row["platform"] - end -end -``` - -This is acceptable because CSV parsing is inherently dynamic and validated by your model. - -## Common Patterns - -### Controllers - -```ruby -# typed: true -class GamesController < ApplicationController - extend T::Sig - - sig { void } - def index - @games = T.let(current_user.games.all, T.untyped) - end - - sig { void } - def show - @game = T.let(current_user.games.find(params[:id]), Game) - end - - sig { void } - def create - @game = T.let(current_user.games.build(game_params), Game) - if @game.save - redirect_to @game - else - render :new - end - end -end -``` - -### Models - -```ruby -# typed: true -class Game < ApplicationRecord - extend T::Sig - - # Class methods - sig { params(query: String).returns(T.untyped) } - def self.search(query) - where("title ILIKE ?", "%#{query}%") - end - - # Instance methods - sig { returns(String) } - def display_title - "#{title} (#{platform&.name || 'Unknown'})" - end - - # Private methods - private - - sig { void } - def set_defaults - self.date_added ||= Date.current - end -end -``` - -### Services - -```ruby -# typed: true -class IgdbService - extend T::Sig - - sig { void } - def initialize - @client_id = T.let(ENV.fetch("IGDB_CLIENT_ID"), String) - @access_token = T.let(get_token, String) - end - - sig { params(title: String, platform: T.nilable(String)).returns(T::Array[T::Hash[Symbol, T.untyped]]) } - def search_game(title, platform: nil) - results = post("/games", build_query(title, platform)) - format_results(results) - end - - private - - sig { params(title: String, platform: T.nilable(String)).returns(String) } - def build_query(title, platform) - "search \"#{title}\";" - end - - sig { params(results: T::Array[T.untyped]).returns(T::Array[T::Hash[Symbol, T.untyped]]) } - def format_results(results) - results.map { |r| { id: r["id"], name: r["name"] } } - end -end -``` - -## Gradual Adoption Strategy - -### Phase 1: Infrastructure (Current) -- โœ… Add Sorbet gems -- โœ… Generate RBI files -- โœ… Add `# typed: true` to key files -- โœ… Type critical methods (User, Game, IgdbService) - -### Phase 2: Core Models (Next) -- Add types to all models -- Type associations and scopes -- Type validations and callbacks - -### Phase 3: Controllers -- Add types to controller actions -- Type params and filters -- Type helper methods - -### Phase 4: Services & Jobs -- Type all service objects -- Type background jobs -- Type API clients - -### Phase 5: Increase Strictness -- Move files from `typed: true` to `typed: strict` -- Remove `T.untyped` where possible -- Add runtime type checks where needed - -## IDE Integration - -### VSCode - -Install the [Sorbet extension](https://marketplace.visualstudio.com/items?itemName=sorbet.sorbet-vscode-extension): - -1. Install extension -2. Sorbet will auto-detect your project -3. Get inline type errors and autocomplete - -### RubyMine - -Sorbet support is built-in: - -1. Enable Sorbet in Settings โ†’ Languages & Frameworks โ†’ Ruby -2. RubyMine will use Sorbet for type checking - -## Updating Types - -When you add gems or change models: - -```bash -# After adding/updating gems -task tapioca:gems - -# After changing models/routes/etc -task tapioca:dsl - -# Both at once -task tapioca:all -``` - -Run this after: -- `bundle install` (new/updated gems) -- Adding/changing models -- Adding/changing routes -- Adding/changing concerns - -## Common Errors - -### "Parent of class redefined" (RDoc, etc.) - -``` -Parent of class RDoc::Markup::Heading redefined from RDoc::Markup::Element to Struct -``` - -**Fix:** This is a known gem incompatibility. Already fixed in `sorbet/config`: -``` ---suppress-payload-superclass-redefinition-for=RDoc::Markup::Heading -``` - -If you see similar errors for other gems, add suppression flags to `sorbet/config`. - -### "Method does not exist" - -``` -app/models/game.rb:10: Method `foo` does not exist on `Game` -``` - -**Fix:** Add the method signature or check for typos. - -### "Argument does not have asserted type" - -``` -Expected String but got T.nilable(String) -``` - -**Fix:** Handle nil case or use `T.must()`: - -```ruby -sig { params(name: String).void } -def greet(name) - puts "Hello #{name}" -end - -# Wrong -greet(user.name) # name might be nil - -# Right -greet(user.name || "Guest") -greet(T.must(user.name)) # Asserts non-nil -``` - -### "Constants must have type annotations" - -```ruby -# Wrong -MY_CONSTANT = "value" - -# Right -MY_CONSTANT = T.let("value", String) -``` - -## Best Practices - -### โœ… DO: -- Start with `# typed: true` (not strict) -- Use `sig` for public methods -- Type critical business logic -- Use `T.untyped` for complex ActiveRecord queries -- Update RBI files after gem/model changes -- Run type checks in CI - -### โŒ DON'T: -- Type everything at once (gradual adoption!) -- Use `# typed: strict` on new code (too strict) -- Ignore type errors (fix or suppress with comment) -- Forget to run `tapioca:dsl` after model changes - -## CI Integration - -Add to `.github/workflows/ci.yml`: - -```yaml -- name: Type check with Sorbet - run: | - bundle exec tapioca gems --no-doc - bundle exec tapioca dsl - bundle exec srb tc -``` - -## Resources - -- [Sorbet Documentation](https://sorbet.org/) -- [Sorbet Playground](https://sorbet.run/) -- [Tapioca Documentation](https://github.com/Shopify/tapioca) -- [T::Types Cheat Sheet](https://sorbet.org/docs/stdlib-generics) - -## Current Status - -**All files start with `# typed: false`** (no checking enabled yet). - -**Ready to type (after running setup):** -- `app/models/user.rb` -- `app/models/game.rb` -- `app/services/igdb_service.rb` -- `app/controllers/games_controller.rb` - -**Recommended typing order:** -1. Start with Models: User, Game, Platform, Genre -2. Then Services: IgdbService -3. Then Controllers: GamesController, CollectionsController -4. Finally Jobs and Helpers - -**Important:** Run the setup steps first: -```bash -bundle install -task tapioca:init -task tapioca:all -task typecheck # Should show "No errors!" -``` - -Then start adding `# typed: true` and signatures incrementally. - ---- - -**Happy typing!** ๐ŸŽ‰ Start small, add types gradually, and enjoy better IDE support and bug catching! diff --git a/docs/THEMES.md b/docs/THEMES.md deleted file mode 100644 index 95f325e..0000000 --- a/docs/THEMES.md +++ /dev/null @@ -1,66 +0,0 @@ -# TurboVault Themes - -## Available Themes - -TurboVault now supports **5 beautiful themes** to customize your experience! - -### ๐ŸŽจ Theme Gallery - -#### โ˜€๏ธ Light (Default) -- Clean, bright interface -- Easy on the eyes during daytime -- Classic Tailwind styling - -#### ๐ŸŒ™ Dark -- Modern dark mode -- Reduced eye strain in low light -- Sleek and professional - -#### ๐ŸŒƒ Midnight -- Deep blue tones -- Perfect for late-night gaming sessions -- Calm and immersive - -#### ๐Ÿ•น๏ธ Retro -- Classic gaming aesthetic -- Brown and gold color scheme -- Nostalgic vibes - -#### ๐ŸŒŠ Ocean -- Blue and teal theme -- Fresh and vibrant -- Aquatic inspiration - -## How to Change Your Theme - -1. Go to **Settings** page -2. Scroll to **Theme** section -3. Click on your preferred theme card -4. Click **"Update Profile"** -5. Enjoy your new look! ๐ŸŽ‰ - -## Technical Details - -- Themes are stored per-user in the database -- Applied via CSS classes on the `` tag -- Works seamlessly with Turbo (no page reload needed) -- Default theme: `light` - -## Adding New Themes - -To add a new theme: - -1. Add validation in `app/models/user.rb` -2. Add theme styles in `app/assets/stylesheets/themes.css` -3. Add theme option in `app/views/users/settings.html.erb` - -## Theme Persistence - -Your theme preference is saved to your account and will persist across: -- Different browsers -- Different devices -- App restarts - -## Browser Support - -Themes work in all modern browsers that support CSS custom properties and Tailwind CSS. diff --git a/k8s/README.md b/k8s/README.md index 440ff61..3152712 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -302,5 +302,5 @@ kubectl delete namespace turbovault ## Support For issues or questions: -- GitHub Issues: https://github.com/yourusername/turbovault/issues -- Documentation: https://github.com/yourusername/turbovault +- GitHub Issues: https://github.com/ryankazokas/turbovault-app/issues +- Documentation: https://github.com/ryankazokas/turbovault-app diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 7e34bf1..b1dc2f5 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -28,10 +28,10 @@ spec: - name: turbovault # UPDATE THIS: Replace with your registry path # Examples: - # - GitHub Container Registry: ghcr.io/username/turbovault:latest + # - GitHub Container Registry: ghcr.io/ryankazokas/turbovault-app:latest # - Docker Hub: docker.io/username/turbovault:latest # - Private registry: registry.example.com/turbovault:latest - image: ghcr.io/username/turbovault:latest + image: ghcr.io/ryankazokas/turbovault-app:latest imagePullPolicy: Always ports: - containerPort: 3000 diff --git a/k8s/migrate-job.yaml b/k8s/migrate-job.yaml index 59add63..f3d9a3a 100644 --- a/k8s/migrate-job.yaml +++ b/k8s/migrate-job.yaml @@ -20,7 +20,7 @@ spec: containers: - name: migrate # UPDATE THIS: Replace with your registry path (same as deployment.yaml) - image: ghcr.io/username/turbovault:latest + image: ghcr.io/ryankazokas/turbovault-app:latest command: ["bundle", "exec", "rails", "db:migrate"] env: # Load from ConfigMap