Page:
Development-Guide
No results
1
Development-Guide
xpltd_admin edited this page 2026-04-03 22:43:20 -06:00
Development Guide
| Meta | Value |
|---|---|
| Repo | xpltdco/tubearr |
| Page | Development-Guide |
| Audience | developers, agents |
| Last Updated | 2026-04-04 |
| Status | current |
Code Organization Patterns
Adding a New API Endpoint
- Create/edit route file in
src/server/routes/— define the Fastify route with method, URL, handler - Register the route in
src/server/index.ts(if new file) —app.register(import('./routes/my-route.js')) - Add repository method in
src/db/repositories/if the endpoint needs new database queries - Add service method in
src/services/if there's business logic beyond simple CRUD - Add frontend API function in
src/frontend/src/api/— create a function that calls the endpoint - Add React Query hook in
src/frontend/src/hooks/— wrap the API function withuseQueryoruseMutation
Adding a New Database Table
- Define schema in
src/db/schema/— create a new file or add to an existing one using Drizzle'ssqliteTable() - Export schema from
src/db/schema/index.ts - Generate migration:
npm run db:generate - Apply migration:
npm run db:migrate - Create repository in
src/db/repositories/— data access functions for the new table - Write tests in
src/__tests__/for the repository
Adding a New Platform Source
- Create source file in
src/sources/— implement thePlatformSourceinterface - Required methods:
resolveChannel(url),fetchRecentContent(channel, mode),getContentMetadata(url) - Register source in
src/index.tsin the PlatformRegistry initialization - Add platform type to the
platformcolumn options insrc/db/schema/channels.ts - Add rate limiter config — new env var
TUBEARR_RATELIMIT_{PLATFORM}_MSinsrc/config/index.ts
Adding a Frontend Page
- Create page component in
src/frontend/src/pages/ - Add route in
src/frontend/src/App.tsx—<Route path="/my-page" element={<MyPage />} /> - Add navigation link in the sidebar/nav component
- Create hooks in
src/frontend/src/hooks/for any API data the page needs
Testing
Test Framework
Vitest — fast, Vite-native test runner with TypeScript support.
Config: vitest.config.ts
- Test files:
src/__tests__/**/*.test.ts - Environment:
node(not jsdom — backend tests) - Timeout: 15 seconds per test
- Path alias:
@/→src/
Running Tests
# Run all tests once
npm test
# Run in watch mode (during development)
npx vitest
# Run a specific test file
npx vitest src/__tests__/services/queue.test.ts
# Run tests matching a pattern
npx vitest --filter "download"
Test Structure
Tests are organized by layer in src/__tests__/:
src/__tests__/
├── services/ # Service-level tests (queue, download, scheduler)
├── repositories/ # Database repository tests
├── routes/ # API route integration tests
├── sources/ # Platform source tests
└── utils/ # Utility function tests
Writing Tests
Tests use Vitest's describe/it/expect API:
import { describe, it, expect, vi } from 'vitest'
describe('QueueService', () => {
it('should enqueue a content item', async () => {
// Arrange
const service = createQueueService(mockDb)
// Act
const result = await service.enqueue(42)
// Assert
expect(result.status).toBe('pending')
expect(result.contentItemId).toBe(42)
})
})
Mocking: Use vi.mock() for module-level mocks and vi.fn() for function mocks. Database tests typically mock the Drizzle client.
CI/CD Pipeline
Forgejo Actions
Workflow: .forgejo/workflows/ci.yml
Triggers: Push or PR to master branch.
Steps:
actions/checkout@v4— checkout codenpm ci— install dependencies (clean install)npx tsc --noEmit— TypeScript type checking (no compilation output)npm test— run Vitest suite
What CI Checks
| Check | Command | Purpose |
|---|---|---|
| Type safety | tsc --noEmit |
Catch type errors without building |
| Tests | npm test |
Run all Vitest test suites |
Local CI Simulation
Run the same checks locally before pushing:
npx tsc --noEmit && npm test
Coding Conventions
TypeScript
- Strict mode enabled (
strict: truein tsconfig) - ES modules — the project uses
"type": "module"in package.json - Path aliases — use
@/to import fromsrc/(e.g.,import { config } from '@/config') - Target: ES2022 — modern JavaScript features available
File Naming
- kebab-case for all files:
format-profile.ts,queue-repository.ts - One module per file — each route, service, repository, or schema gets its own file
- Test files mirror source structure:
src/services/queue.ts→src/__tests__/services/queue.test.ts
Code Patterns
- Repository pattern — all database access goes through repository functions, never direct Drizzle calls in routes
- Service pattern — business logic lives in services, routes are thin handlers that validate input and call services
- Fastify plugins — routes are registered as Fastify plugins using
fastify-plugin - Error classification — yt-dlp errors are classified into categories (
rate_limit,geo_blocked, etc.) for smart retry logic
Import Style
// External dependencies
import Fastify from 'fastify'
import { eq } from 'drizzle-orm'
// Internal imports (use path alias)
import { config } from '@/config'
import { channels } from '@/db/schema'
import { getChannel } from '@/db/repositories/channel-repository'
Branch Strategy
master— primary branch, CI runs on every push- Feature branches for non-trivial changes, merged via PR
- No formal branching model (e.g., GitFlow) — keep it simple
Development Tips
Hot Reload
npm run dev uses tsx watch — the backend restarts automatically when you save a TypeScript file. The frontend uses Vite HMR for instant updates without full page reload.
Database Inspection
To inspect the SQLite database directly:
# Local dev
sqlite3 ./data/tubearr.db
# Docker
docker exec -it tubearr sqlite3 /config/tubearr.db
# Useful queries
.tables -- list all tables
.schema channels -- show table schema
SELECT * FROM system_config; -- view app settings
SELECT COUNT(*) FROM content_items; -- count content items
Debugging yt-dlp
Test yt-dlp commands directly to debug download issues:
# Check if yt-dlp can access a URL
yt-dlp --dump-json "https://youtube.com/watch?v=xxx"
# Test format selection
yt-dlp -F "https://youtube.com/watch?v=xxx"
# Simulate a download (dry run)
yt-dlp --simulate "https://youtube.com/watch?v=xxx"
API Testing
Use curl with the API key to test endpoints:
# List channels
curl -H "X-Api-Key: your-key" http://localhost:8989/api/v1/channel
# Add a channel
curl -X POST -H "X-Api-Key: your-key" -H "Content-Type: application/json" \
-d '{"url": "https://youtube.com/@example"}' \
http://localhost:8989/api/v1/channel
# Check queue
curl -H "X-Api-Key: your-key" http://localhost:8989/api/v1/queue