mirror of
https://github.com/ryankazokas/turbovault-app.git
synced 2026-04-16 22:12:53 +00:00
Condenses Documentation
This commit is contained in:
6
.github/SECRETS_SETUP.md
vendored
6
.github/SECRETS_SETUP.md
vendored
@@ -12,7 +12,7 @@ git tag v1.0.0
|
|||||||
git push origin 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
|
## 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
|
1. Go to your GitHub profile
|
||||||
2. Click **Packages** tab
|
2. Click **Packages** tab
|
||||||
3. You'll see `turbovault` package
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ Your repository includes these workflows:
|
|||||||
## Support
|
## Support
|
||||||
|
|
||||||
If you encounter issues:
|
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
|
2. Read the error messages carefully
|
||||||
3. For custom registries, test login locally first
|
3. For custom registries, test login locally first
|
||||||
4. Open an issue if you're stuck
|
4. Open an issue if you're stuck
|
||||||
|
|||||||
242
README.md
242
README.md
@@ -1,212 +1,82 @@
|
|||||||
# 🎮 TurboVault
|
# 🎮 TurboVault
|
||||||
|
|
||||||
> Your personal video game collection tracker and manager
|
> Your personal video game collection tracker
|
||||||
|
|
||||||
[](https://rubyonrails.org/)
|
[](https://rubyonrails.org/)
|
||||||

|

|
||||||
[](https://github.com/rubocop/rubocop)
|
|
||||||
[](https://sorbet.org)
|
|
||||||
[](https://www.ruby-lang.org/)
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
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
|
- 📦 Track physical & digital games
|
||||||
- 🎮 **IGDB Integration** - Automatic game matching with cover art and metadata
|
- 🎯 Collections & subcollections
|
||||||
- 📊 **Collection Statistics** - Track spending, completion rates, and platform distribution
|
- 🔍 Search, filter, sort
|
||||||
- 🗂️ **Collections** - Organize games into custom collections
|
- 📊 Statistics dashboard
|
||||||
- 🔍 **Smart Search** - Find games quickly with advanced filtering
|
- 📥 CSV bulk import
|
||||||
- 📥 **CSV Import** - Bulk import your existing collection
|
- 🎮 IGDB metadata matching
|
||||||
- 🎨 **5 Beautiful Themes** - Light, Dark, Midnight, Retro, and Ocean
|
- 🔌 RESTful API
|
||||||
- 🔐 **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
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Ruby 3.3+
|
|
||||||
- PostgreSQL 15+
|
|
||||||
- Node.js 18+ (for asset compilation)
|
|
||||||
- Docker (optional, for containerized development)
|
|
||||||
|
|
||||||
### Local Development
|
### 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
|
```bash
|
||||||
# 1. Push to GitHub
|
# Start services
|
||||||
git push origin main
|
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
|
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
|
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
|
./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
|
```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)
|
MIT - See [LICENSE](LICENSE)
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -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
|
|
||||||

|
|
||||||
[](https://github.com/rubocop/rubocop)
|
|
||||||
[](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`
|
|
||||||
@@ -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! 🚀
|
|
||||||
@@ -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)
|
- Kubernetes cluster
|
||||||
2. [Kubernetes Deployment](#kubernetes-deployment)
|
- kubectl configured
|
||||||
3. [Database Setup](#database-setup)
|
- PostgreSQL database (already set up)
|
||||||
4. [DNS & SSL](#dns--ssl)
|
|
||||||
5. [Monitoring](#monitoring)
|
|
||||||
|
|
||||||
---
|
## Quick Deploy
|
||||||
|
|
||||||
## GitHub Setup
|
|
||||||
|
|
||||||
### Push to GitHub
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the automated setup script
|
# 1. Build image (auto-builds via GitHub Actions)
|
||||||
./scripts/setup-github.sh
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
# Or manually:
|
# 2. Configure secrets
|
||||||
git init
|
cp k8s/secrets.yaml.example k8s/secrets.yaml
|
||||||
git add .
|
nano k8s/secrets.yaml
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Set Up GitHub Actions (Optional)
|
# 3. Deploy
|
||||||
|
|
||||||
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
|
|
||||||
./scripts/deploy-k8s.sh
|
./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
|
```bash
|
||||||
# 1. Login to Gitea registry
|
# Generate Rails secret
|
||||||
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
|
|
||||||
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/namespace.yaml
|
||||||
kubectl apply -f k8s/configmap.yaml
|
kubectl apply -f k8s/configmap.yaml
|
||||||
kubectl apply -f k8s/secrets.yaml
|
kubectl apply -f k8s/secrets.yaml
|
||||||
@@ -116,117 +73,27 @@ kubectl apply -f k8s/service.yaml
|
|||||||
kubectl apply -f k8s/ingress.yaml
|
kubectl apply -f k8s/ingress.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Image Reference
|
## Update Deployment
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add Bitnami repo
|
# Build new version
|
||||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
git tag v1.1.0
|
||||||
helm repo update
|
git push origin v1.1.0
|
||||||
|
|
||||||
# Install PostgreSQL
|
# Update deployment
|
||||||
helm install postgres bitnami/postgresql \
|
kubectl set image deployment/turbovault \
|
||||||
--namespace turbovault \
|
turbovault=ghcr.io/ryankazokas/turbovault-app:v1.1.0 \
|
||||||
--set auth.database=turbovault_production \
|
-n turbovault
|
||||||
--set auth.username=turbovault \
|
|
||||||
--set auth.password=changeme \
|
|
||||||
--set primary.persistence.size=10Gi
|
|
||||||
|
|
||||||
# Connection details
|
# Watch rollout
|
||||||
DATABASE_HOST: postgres-postgresql
|
kubectl rollout status deployment/turbovault -n turbovault
|
||||||
DATABASE_PORT: 5432
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## SSL/TLS
|
||||||
|
|
||||||
## DNS & SSL
|
Update `k8s/ingress.yaml` with your domain and TLS configuration.
|
||||||
|
|
||||||
### Configure DNS
|
For Let's Encrypt with cert-manager:
|
||||||
|
|
||||||
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`:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
metadata:
|
metadata:
|
||||||
@@ -235,68 +102,17 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- turbovault.example.com
|
- turbovault.yourdomain.com
|
||||||
secretName: turbovault-tls
|
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
|
## Scaling
|
||||||
|
|
||||||
### Horizontal Scaling
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Scale to 3 replicas
|
# Scale replicas
|
||||||
kubectl scale deployment turbovault --replicas=3 -n turbovault
|
kubectl scale deployment turbovault --replicas=3 -n turbovault
|
||||||
|
|
||||||
# Auto-scaling (HPA)
|
# Auto-scaling
|
||||||
kubectl autoscale deployment turbovault \
|
kubectl autoscale deployment turbovault \
|
||||||
--cpu-percent=70 \
|
--cpu-percent=70 \
|
||||||
--min=2 \
|
--min=2 \
|
||||||
@@ -304,73 +120,33 @@ kubectl autoscale deployment turbovault \
|
|||||||
-n turbovault
|
-n turbovault
|
||||||
```
|
```
|
||||||
|
|
||||||
### Vertical Scaling
|
## Monitoring
|
||||||
|
|
||||||
Edit `k8s/deployment.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "2Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backup & Restore
|
|
||||||
|
|
||||||
### Database Backup
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Automated backup (cronjob)
|
# Check status
|
||||||
kubectl create cronjob pg-backup \
|
kubectl get pods -n turbovault
|
||||||
--image=postgres:15 \
|
|
||||||
--schedule="0 2 * * *" \
|
# View logs
|
||||||
--restart=Never \
|
kubectl logs -f -l app=turbovault -n turbovault
|
||||||
-n turbovault \
|
|
||||||
-- /bin/sh -c "pg_dump -h postgres-postgresql -U turbovault turbovault_production | gzip > /backup/turbovault-$(date +%Y%m%d).sql.gz"
|
# Check resources
|
||||||
|
kubectl top pods -n turbovault
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Backup
|
## Troubleshooting
|
||||||
|
|
||||||
|
### View pod details
|
||||||
```bash
|
```bash
|
||||||
# Backup all k8s resources
|
kubectl describe pod -l app=turbovault -n turbovault
|
||||||
kubectl get all -n turbovault -o yaml > turbovault-backup.yaml
|
|
||||||
|
|
||||||
# Backup secrets (encrypted)
|
|
||||||
kubectl get secrets -n turbovault -o yaml > secrets-backup.yaml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Test database connection
|
||||||
|
```bash
|
||||||
|
kubectl exec -it deployment/turbovault -n turbovault -- \
|
||||||
|
rails runner "puts ActiveRecord::Base.connection.execute('SELECT 1').first"
|
||||||
|
```
|
||||||
|
|
||||||
## Security Best Practices
|
### View environment
|
||||||
|
```bash
|
||||||
1. ✅ Use Kubernetes Secrets (or Sealed Secrets)
|
kubectl exec -it deployment/turbovault -n turbovault -- env | grep DATABASE
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,684 +1,124 @@
|
|||||||
# TurboVault - Development Guide
|
# Development Guide
|
||||||
|
|
||||||
## Quick Start
|
## Prerequisites
|
||||||
|
|
||||||
|
- Ruby 3.3+
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start PostgreSQL
|
# Clone repository
|
||||||
|
git clone https://github.com/ryankazokas/turbovault-app.git
|
||||||
|
cd turbovault
|
||||||
|
|
||||||
|
# Start services (PostgreSQL + Mailpit)
|
||||||
task docker:up
|
task docker:up
|
||||||
|
|
||||||
# Setup database (first time only)
|
# Setup database
|
||||||
task db:setup
|
task db:setup
|
||||||
|
|
||||||
# Start Rails server
|
# Run development server
|
||||||
task server
|
task dev
|
||||||
|
|
||||||
# Or use bin/dev for Tailwind watch mode
|
|
||||||
bin/dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit http://localhost:3000 and create an account!
|
Visit http://localhost:3000
|
||||||
|
|
||||||
## Development Workflow
|
**Demo:** `demo@turbovault.com` / `password123`
|
||||||
|
|
||||||
### Daily Development
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start all services
|
# Development
|
||||||
task docker:up # PostgreSQL
|
task dev # Start Rails + CSS watcher
|
||||||
bin/dev # Rails server + Tailwind 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
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
app/
|
app/
|
||||||
├── controllers/
|
├── controllers/ # Controllers
|
||||||
│ ├── concerns/ # Shared controller modules
|
├── models/ # Models
|
||||||
│ ├── api/v1/ # API controllers
|
├── views/ # ERB templates
|
||||||
│ └── *.rb # Web controllers
|
├── javascript/ # Stimulus controllers
|
||||||
├── models/
|
├── services/ # Service objects (IgdbService)
|
||||||
│ ├── concerns/ # Shared model modules
|
└── jobs/ # Background jobs (IgdbSyncJob)
|
||||||
│ └── *.rb # ActiveRecord models
|
|
||||||
├── views/
|
|
||||||
│ ├── layouts/ # Application layouts
|
|
||||||
│ └── */ # Controller-specific views
|
|
||||||
├── helpers/ # View helpers
|
|
||||||
├── mailers/ # Email mailers
|
|
||||||
└── jobs/ # Background jobs
|
|
||||||
|
|
||||||
config/
|
config/
|
||||||
├── routes.rb # URL routing
|
├── routes.rb # Routes
|
||||||
├── database.yml # Database configuration
|
├── database.yml # Database config
|
||||||
└── environments/ # Environment-specific config
|
├── queue.yml # Solid Queue config
|
||||||
|
└── recurring.yml # Recurring jobs (IGDB sync)
|
||||||
|
|
||||||
db/
|
k8s/ # Kubernetes manifests
|
||||||
├── migrate/ # Database migrations
|
docs/ # Documentation
|
||||||
├── 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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
Create `.env` for development (never commit!):
|
Copy `.env.example` to `.env`:
|
||||||
```
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# First time setup
|
# Optional: IGDB API
|
||||||
kamal setup
|
IGDB_CLIENT_ID=your_client_id
|
||||||
|
IGDB_CLIENT_SECRET=your_client_secret
|
||||||
# Deploy
|
|
||||||
kamal deploy
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
kamal app exec --interactive --reuse "bin/rails console"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Railway/Render
|
Get credentials: https://dev.twitch.tv
|
||||||
|
|
||||||
1. Push to Git
|
## Contributing
|
||||||
2. Connect repository
|
|
||||||
3. Set environment variables
|
1. Fork repository
|
||||||
4. Add build command: `bundle install && rails db:migrate`
|
2. Create feature branch
|
||||||
5. Add start command: `rails server -b 0.0.0.0`
|
3. Make changes
|
||||||
|
4. Run: `task test && task lint:fix && task typecheck`
|
||||||
|
5. Submit PR
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Database Connection Errors
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if PostgreSQL is running
|
# Database issues
|
||||||
docker compose ps
|
task docker:up && task db:reset
|
||||||
|
|
||||||
# Start PostgreSQL
|
# Type errors
|
||||||
task docker:up
|
task tapioca:all && task typecheck
|
||||||
|
|
||||||
# Check database configuration
|
# Tests failing
|
||||||
cat config/database.yml
|
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! 🚀
|
|
||||||
|
|||||||
@@ -1,391 +1,114 @@
|
|||||||
# IGDB Integration Guide
|
# IGDB Integration
|
||||||
|
|
||||||
## Overview
|
TurboVault integrates with the [Internet Game Database (IGDB)](https://www.igdb.com/) to automatically enrich your games with metadata, cover art, and genre information.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
### 1. **User Opt-In**
|
1. **Background sync** runs every 30 minutes (configurable)
|
||||||
- Users must enable IGDB sync in Settings
|
2. **Searches IGDB** for games without metadata
|
||||||
- Default: OFF (privacy by design)
|
3. **Creates suggestions** with confidence scores (0-100%)
|
||||||
- Can be toggled on/off anytime
|
4. **Users review** and approve/reject matches
|
||||||
|
5. **Metadata applied** when approved (cover art, genres, release dates)
|
||||||
|
|
||||||
### 2. **Automatic Sync Job**
|
## Setup
|
||||||
- Runs every 30 minutes
|
|
||||||
- Only processes users with sync enabled
|
|
||||||
- Matches games that don't have IGDB IDs yet
|
|
||||||
|
|
||||||
### 3. **Smart Matching**
|
### Get IGDB Credentials
|
||||||
- Searches IGDB using game title + platform
|
|
||||||
- Returns top 3 matches with confidence scores
|
|
||||||
- Uses fuzzy matching and platform filtering
|
|
||||||
|
|
||||||
### 4. **User Review**
|
1. Create account at https://dev.twitch.tv
|
||||||
- Users review suggested matches
|
2. Register an application
|
||||||
- See cover art, release year, platform
|
3. Copy Client ID and Client Secret
|
||||||
- Approve or reject each match
|
|
||||||
- Even high-confidence matches require approval (prevents errors)
|
|
||||||
|
|
||||||
### 5. **Public Cache**
|
### Configure
|
||||||
- Matched games stored in `igdb_games` table
|
|
||||||
- Shared across all users (public data only)
|
|
||||||
- Reduces API calls for common games
|
|
||||||
|
|
||||||
## 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
|
```bash
|
||||||
IGDB_CLIENT_ID=your_client_id
|
IGDB_CLIENT_ID=your_client_id_here
|
||||||
IGDB_ACCESS_TOKEN=your_token
|
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
|
## Usage
|
||||||
|
|
||||||
### For Users
|
### Review Matches
|
||||||
|
|
||||||
1. **Enable Sync**
|
Visit `/igdb_matches` to see pending suggestions.
|
||||||
- Go to Settings
|
|
||||||
- Check "Enable IGDB game matching"
|
|
||||||
- Click "Update Profile"
|
|
||||||
|
|
||||||
2. **Trigger Sync**
|
Each suggestion shows:
|
||||||
- Go to IGDB Matches page
|
- Game title
|
||||||
- Click "Sync Now"
|
- Cover art
|
||||||
- Wait a few minutes
|
- Platform
|
||||||
|
- Confidence score
|
||||||
|
- Genres from IGDB
|
||||||
|
|
||||||
3. **Review Matches**
|
**Actions:**
|
||||||
- View pending matches
|
- ✅ **Approve** - Apply metadata to your game
|
||||||
- See confidence scores
|
- ❌ **Reject** - Dismiss suggestion
|
||||||
- See cover art and release year
|
- 🔄 **Sync Now** - Manually trigger sync for your games
|
||||||
- Approve correct matches
|
|
||||||
- Reject incorrect ones
|
|
||||||
|
|
||||||
4. **Check Progress**
|
### Manual Commands
|
||||||
- View stats: Matched, Unmatched, Pending Review
|
|
||||||
- See last sync time
|
|
||||||
- Badge in navigation shows pending count
|
|
||||||
|
|
||||||
### 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
|
```bash
|
||||||
tail -f log/development.log | grep IGDB
|
# Trigger sync manually
|
||||||
```
|
task igdb:sync
|
||||||
|
|
||||||
**Rails Console:**
|
|
||||||
```ruby
|
|
||||||
# Check sync status
|
# Check sync status
|
||||||
User.find_by(email: "demo@turbovault.com").igdb_sync_enabled
|
task igdb:status
|
||||||
|
|
||||||
# View pending matches
|
# Clear stuck jobs
|
||||||
User.first.igdb_match_suggestions.status_pending
|
task igdb:clear
|
||||||
|
|
||||||
# Test IGDB service
|
|
||||||
service = IgdbService.new
|
|
||||||
results = service.search_game("Mario 64", Platform.find_by(abbreviation: "N64"))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Summary
|
## How Matching Works
|
||||||
|
|
||||||
The IGDB integration is now fully functional:
|
**Confidence scoring:**
|
||||||
- ✅ User opt-in settings
|
- Title match: 0-70 points
|
||||||
- ✅ Automatic sync every 30 minutes
|
- Platform match: 0-30 points
|
||||||
- ✅ Smart matching with confidence scores
|
- 100% = exact title + platform match
|
||||||
- ✅ Review UI with cover art
|
|
||||||
- ✅ Public game cache
|
|
||||||
- ✅ Rate limiting and error handling
|
|
||||||
- ✅ Privacy-preserving design
|
|
||||||
|
|
||||||
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).
|
||||||
|
|||||||
@@ -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
|
### Prerequisites
|
||||||
- Kubernetes cluster (k3s, minikube, EKS, GKE, etc.)
|
|
||||||
- kubectl configured
|
|
||||||
- PostgreSQL database (or use in-cluster Helm chart)
|
|
||||||
|
|
||||||
## Step 1: Push to GitHub
|
- Ruby 3.3+
|
||||||
|
- Docker (for services)
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd turbovault-web
|
# Clone
|
||||||
|
git clone https://github.com/ryankazokas/turbovault-app.git
|
||||||
|
cd turbovault
|
||||||
|
|
||||||
# Initialize git
|
# Start services (PostgreSQL + Mailpit)
|
||||||
git init
|
task docker:up
|
||||||
git add .
|
|
||||||
git commit -m "Initial commit: TurboVault"
|
|
||||||
|
|
||||||
# Add your GitHub remote
|
# Setup database
|
||||||
git remote add origin https://github.com/YOUR_USERNAME/turbovault.git
|
task db:setup
|
||||||
git push -u origin main
|
|
||||||
|
# Start server
|
||||||
|
task dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Tag a Release
|
Visit **http://localhost:3000**
|
||||||
|
|
||||||
```bash
|
### Demo Account
|
||||||
git tag v1.0.0
|
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
**What happens:**
|
- Email: `demo@turbovault.com`
|
||||||
- GitHub Actions automatically triggers
|
- Password: `password123`
|
||||||
- Builds Docker image
|
|
||||||
- Pushes to GitHub Container Registry (ghcr.io)
|
|
||||||
- Image: `ghcr.io/YOUR_USERNAME/turbovault:v1.0.0`
|
|
||||||
|
|
||||||
**Check progress:** GitHub → Actions tab
|
## Optional: IGDB Integration
|
||||||
|
|
||||||
## Step 3: Prepare Kubernetes Secrets
|
For automatic game metadata:
|
||||||
|
|
||||||
```bash
|
1. Create app at https://dev.twitch.tv
|
||||||
# Copy the template
|
2. Add to `.env`:
|
||||||
cp k8s/secrets.yaml.example k8s/secrets.yaml
|
```bash
|
||||||
|
IGDB_CLIENT_ID=your_id
|
||||||
|
IGDB_CLIENT_SECRET=your_secret
|
||||||
|
```
|
||||||
|
3. Restart server
|
||||||
|
|
||||||
# Generate Rails secret
|
## Deployment
|
||||||
rails secret
|
|
||||||
# Copy output
|
|
||||||
|
|
||||||
# Edit secrets.yaml
|
For production deployment to Kubernetes, see [Deployment Guide](DEPLOYMENT.md).
|
||||||
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! 🎉
|
|
||||||
|
|||||||
@@ -1,45 +1,14 @@
|
|||||||
# TurboVault Documentation
|
# 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
|
## Demo
|
||||||
- [Main README](../README.md) - Project overview
|
|
||||||
- ⭐ [Quick Start Guide](QUICK_START.md) - **Deploy in minutes!**
|
|
||||||
- [Demo Account](DEMO_ACCOUNT.md) - Try the demo
|
|
||||||
|
|
||||||
### Deployment & Development
|
Email: `demo@turbovault.com`
|
||||||
- [Deployment Guide](DEPLOYMENT.md) - Complete deployment reference
|
Password: `password123`
|
||||||
- [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).
|
|
||||||
|
|||||||
520
docs/SORBET.md
520
docs/SORBET.md
@@ -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!
|
|
||||||
@@ -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.
|
|
||||||
@@ -302,5 +302,5 @@ kubectl delete namespace turbovault
|
|||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
- GitHub Issues: https://github.com/yourusername/turbovault/issues
|
- GitHub Issues: https://github.com/ryankazokas/turbovault-app/issues
|
||||||
- Documentation: https://github.com/yourusername/turbovault
|
- Documentation: https://github.com/ryankazokas/turbovault-app
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ spec:
|
|||||||
- name: turbovault
|
- name: turbovault
|
||||||
# UPDATE THIS: Replace with your registry path
|
# UPDATE THIS: Replace with your registry path
|
||||||
# Examples:
|
# 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
|
# - Docker Hub: docker.io/username/turbovault:latest
|
||||||
# - Private registry: registry.example.com/turbovault:latest
|
# - Private registry: registry.example.com/turbovault:latest
|
||||||
image: ghcr.io/username/turbovault:latest
|
image: ghcr.io/ryankazokas/turbovault-app:latest
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: migrate
|
- name: migrate
|
||||||
# UPDATE THIS: Replace with your registry path (same as deployment.yaml)
|
# 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"]
|
command: ["bundle", "exec", "rails", "db:migrate"]
|
||||||
env:
|
env:
|
||||||
# Load from ConfigMap
|
# Load from ConfigMap
|
||||||
|
|||||||
Reference in New Issue
Block a user