Condenses Documentation

This commit is contained in:
2026-03-29 02:55:42 -04:00
parent 323484a33a
commit 55fe839882
14 changed files with 363 additions and 2971 deletions

View File

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

View File

@@ -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! 🚀

View File

@@ -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 <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
```
Update `k8s/ingress.yaml`:
For Let's Encrypt with cert-manager:
```yaml
metadata:
@@ -235,68 +102,17 @@ metadata:
spec:
tls:
- hosts:
- turbovault.example.com
- turbovault.yourdomain.com
secretName: turbovault-tls
```
---
## Monitoring
### Health Checks
```bash
# Check pod health
kubectl get pods -n turbovault
# Check application health
kubectl port-forward svc/turbovault-service 3000:80 -n turbovault
# Visit http://localhost:3000/up
```
### View Logs
```bash
# All pods
kubectl logs -f -l app=turbovault -n turbovault
# Specific pod
kubectl logs -f turbovault-xxxxx-xxxxx -n turbovault
# Previous logs (if crashed)
kubectl logs --previous turbovault-xxxxx-xxxxx -n turbovault
```
### Common Issues
**Pods not starting:**
```bash
kubectl describe pod -l app=turbovault -n turbovault
```
**Database connection failed:**
```bash
# Test connection
kubectl run -it --rm debug --image=postgres:15 --restart=Never -n turbovault -- \
psql -h postgres-postgresql -U turbovault -d turbovault_production
```
**Migration failed:**
```bash
kubectl logs job/turbovault-migrate -n turbovault
```
---
## Scaling
### Horizontal Scaling
```bash
# Scale to 3 replicas
# Scale replicas
kubectl scale deployment turbovault --replicas=3 -n turbovault
# Auto-scaling (HPA)
# Auto-scaling
kubectl autoscale deployment turbovault \
--cpu-percent=70 \
--min=2 \
@@ -304,73 +120,33 @@ kubectl autoscale deployment turbovault \
-n turbovault
```
### Vertical Scaling
Edit `k8s/deployment.yaml`:
```yaml
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
```
---
## Backup & Restore
### Database Backup
## Monitoring
```bash
# Automated backup (cronjob)
kubectl create cronjob pg-backup \
--image=postgres:15 \
--schedule="0 2 * * *" \
--restart=Never \
-n turbovault \
-- /bin/sh -c "pg_dump -h postgres-postgresql -U turbovault turbovault_production | gzip > /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
```

View File

@@ -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! 🚀

View File

@@ -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).

View File

@@ -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).

View File

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

View File

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

View File

@@ -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 `<body>` 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.