* fix: replace 6-word limit with 150-character limit for project names Replaces the word-based validation with character-based validation as originally requested in #971. The 6-word limit was causing issues with small words and separators being counted equally, and didn't match the original requirement for a character limit. Changes: - Backend: Replace wordCount validator with len validator (1-150 chars) - Frontend: Replace word count validation with character length check - UI already has line-clamp-3 for display truncation Fixes #998 * fix: make password_digest migration compatible with all schema versions Fixes a critical bug where the make-password-optional migration would silently fail when upgrading from v1.0.0 or running on fresh v1.1.0-dev installations. The migration was trying to SELECT columns (ai_provider, openai_api_key, ollama_base_url, ollama_model) that don't exist in the users table at that point in the migration chain, causing the INSERT...SELECT to fail and leaving password_digest as NOT NULL. This prevented OIDC auto-provisioning from creating new users without passwords. The fix dynamically detects which columns exist in the users table using PRAGMA table_info and only selects columns that are guaranteed to exist. Missing columns (AI-related fields) will receive their default values from the new table schema. Changes: - Added dynamic column detection using PRAGMA table_info - Only SELECT columns that exist in the current users table - AI columns get default values if they don't exist yet - Applied same fix to both up and down migrations - Properly handle password/password_digest column name migration Fixes #1075 * feat: add configurable file upload limit via environment variable Add FILE_UPLOAD_LIMIT_MB environment variable to make file upload limits configurable. Previously hardcoded at 10MB, users can now customize this via Docker environment variables or .env configuration to support larger file attachments. Changes: - Add FILE_UPLOAD_LIMIT_MB config with 10MB default fallback - Update multer limits in tasks/attachments and projects routes - Update Express body parser limits to use dynamic config - Add /api/config endpoint to expose file limit to frontend - Update frontend validation to fetch and use server config - Add configService.ts for caching server configuration - Update documentation with new environment variable Fixes #1000
581 lines
10 KiB
Markdown
581 lines
10 KiB
Markdown
# Development Workflow
|
|
|
|
[← Back to Index](../CLAUDE.md)
|
|
|
|
---
|
|
|
|
## Initial Setup
|
|
|
|
### Prerequisites
|
|
|
|
- **Node.js** v22+ (recommended - check package.json engines field)
|
|
- **npm** (comes with Node.js)
|
|
- **Git**
|
|
|
|
### Clone and Install
|
|
|
|
```bash
|
|
# Clone repository
|
|
git clone https://github.com/chrisvel/tududi.git
|
|
cd tududi
|
|
|
|
# Install all dependencies
|
|
# This installs both frontend and backend dependencies (monorepo setup)
|
|
npm install
|
|
```
|
|
|
|
### Initialize Database
|
|
|
|
```bash
|
|
# Create database and run all migrations
|
|
npm run db:init
|
|
|
|
# This command:
|
|
# 1. Creates /backend/database.sqlite
|
|
# 2. Runs all migrations from /backend/migrations/
|
|
# 3. Sets up tables and relationships
|
|
```
|
|
|
|
### Create Test User (Optional)
|
|
|
|
```bash
|
|
npm run user:create
|
|
|
|
# Interactive prompts:
|
|
# - Email address
|
|
# - Password
|
|
# - Timezone (defaults to system timezone)
|
|
```
|
|
|
|
---
|
|
|
|
## Daily Development
|
|
|
|
Tududi runs two separate processes during development:
|
|
|
|
### Two-Server Development
|
|
|
|
**Terminal 1 - Backend (Express):**
|
|
```bash
|
|
npm run backend:dev
|
|
|
|
# Details:
|
|
# - Runs: nodemon backend/app.js
|
|
# - Server: http://localhost:3002
|
|
# - Auto-reloads: Yes (on file changes)
|
|
# - API endpoints: /api/v1/*
|
|
# - Swagger docs: /api-docs (after login)
|
|
```
|
|
|
|
**Terminal 2 - Frontend (Webpack Dev Server):**
|
|
```bash
|
|
npm run frontend:dev
|
|
|
|
# Details:
|
|
# - Runs: webpack serve --mode development
|
|
# - Server: http://localhost:8080
|
|
# - Hot reload: Yes (React Fast Refresh)
|
|
# - Proxies /api/* to backend:3002
|
|
# - Proxies /locales/* to backend:3002
|
|
```
|
|
|
|
**Or run both simultaneously:**
|
|
```bash
|
|
npm start
|
|
|
|
# Runs both backend:dev and frontend:dev in parallel
|
|
# Uses 'concurrently' package
|
|
# Logs from both processes interleaved
|
|
```
|
|
|
|
### Accessing the Application
|
|
|
|
Open http://localhost:8080 in your browser.
|
|
|
|
**Login with:**
|
|
- Email: The email you set during db:init or user:create
|
|
- Password: The password you set
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
Create `/backend/.env` file (not tracked in git):
|
|
|
|
```bash
|
|
# Required
|
|
TUDUDI_SESSION_SECRET=your-random-secret-here-use-openssl-rand-hex-64
|
|
TUDUDI_USER_EMAIL=admin@example.com
|
|
TUDUDI_USER_PASSWORD=your-secure-password
|
|
|
|
# Optional - Server config
|
|
NODE_ENV=development
|
|
DB_FILE=database.sqlite
|
|
FRONTEND_URL=http://localhost:8080
|
|
BACKEND_URL=http://localhost:3002
|
|
PORT=3002
|
|
HOST=0.0.0.0
|
|
FILE_UPLOAD_LIMIT_MB=10
|
|
|
|
# Optional - Email
|
|
ENABLE_EMAIL=false
|
|
EMAIL_SMTP_HOST=smtp.example.com
|
|
EMAIL_SMTP_PORT=587
|
|
EMAIL_SMTP_SECURE=false
|
|
EMAIL_SMTP_USERNAME=user
|
|
EMAIL_SMTP_PASSWORD=pass
|
|
EMAIL_FROM_ADDRESS=noreply@example.com
|
|
EMAIL_FROM_NAME=Tududi
|
|
|
|
# Optional - Integrations
|
|
DISABLE_TELEGRAM=false
|
|
GOOGLE_CLIENT_ID=your-google-oauth-client-id
|
|
GOOGLE_CLIENT_SECRET=your-google-oauth-secret
|
|
GOOGLE_REDIRECT_URI=http://localhost:8080/auth/google/callback
|
|
|
|
# Optional - Features
|
|
DISABLE_SCHEDULER=false
|
|
SWAGGER_ENABLED=true
|
|
RATE_LIMITING_ENABLED=true
|
|
|
|
# Optional - Proxy (if behind reverse proxy)
|
|
TUDUDI_TRUST_PROXY=false
|
|
TUDUDI_ALLOWED_ORIGINS=http://localhost:8080
|
|
|
|
# Optional - Registration
|
|
REGISTRATION_TOKEN_EXPIRY_HOURS=24
|
|
```
|
|
|
|
**Generate secure session secret:**
|
|
```bash
|
|
openssl rand -hex 64
|
|
```
|
|
|
|
---
|
|
|
|
## Adding a New Feature (Complete Example)
|
|
|
|
**Example: Add "estimated_time" field to tasks**
|
|
|
|
This walkthrough shows all files to touch when adding a new field to an existing model.
|
|
|
|
### Step 1: Create Database Migration
|
|
|
|
```bash
|
|
npm run migration:create -- --name add-estimated-time-to-tasks
|
|
```
|
|
|
|
Edit the created file `/backend/migrations/YYYYMMDDHHMMSS-add-estimated-time-to-tasks.js`:
|
|
|
|
```javascript
|
|
'use strict';
|
|
|
|
module.exports = {
|
|
async up(queryInterface, Sequelize) {
|
|
await queryInterface.addColumn('Tasks', 'estimated_time', {
|
|
type: Sequelize.INTEGER, // minutes
|
|
allowNull: true,
|
|
defaultValue: null
|
|
});
|
|
},
|
|
|
|
async down(queryInterface, Sequelize) {
|
|
await queryInterface.removeColumn('Tasks', 'estimated_time');
|
|
}
|
|
};
|
|
```
|
|
|
|
**Run migration:**
|
|
```bash
|
|
npm run migration:run
|
|
```
|
|
|
|
### Step 2: Update Sequelize Model
|
|
|
|
Edit `/backend/models/task.js`:
|
|
|
|
```javascript
|
|
Task.init({
|
|
// ... existing fields ...
|
|
estimated_time: {
|
|
type: DataTypes.INTEGER,
|
|
allowNull: true,
|
|
comment: 'Estimated time in minutes'
|
|
},
|
|
// ... rest of fields ...
|
|
}, {
|
|
sequelize,
|
|
modelName: 'Task',
|
|
tableName: 'Tasks'
|
|
});
|
|
```
|
|
|
|
### Step 3: Update Serializer (API Response)
|
|
|
|
Edit `/backend/modules/tasks/core/serializers.js`:
|
|
|
|
```javascript
|
|
function serializeTask(task) {
|
|
return {
|
|
// ... existing fields ...
|
|
estimated_time: task.estimated_time,
|
|
// ... rest of fields ...
|
|
};
|
|
}
|
|
```
|
|
|
|
### Step 4: Update Builder (API Input)
|
|
|
|
Edit `/backend/modules/tasks/core/builders.js`:
|
|
|
|
```javascript
|
|
function buildTaskAttributes(data, userId) {
|
|
const attributes = {
|
|
// ... existing fields ...
|
|
estimated_time: data.estimated_time ? parseInt(data.estimated_time, 10) : null,
|
|
// ... rest of fields ...
|
|
};
|
|
|
|
return attributes;
|
|
}
|
|
```
|
|
|
|
### Step 5: Add Validation (Optional)
|
|
|
|
If validation needed, edit `/backend/modules/tasks/routes.js`:
|
|
|
|
```javascript
|
|
router.put('/task/:id', async (req, res, next) => {
|
|
try {
|
|
// Validate estimated_time
|
|
if (req.body.estimated_time !== undefined) {
|
|
const time = parseInt(req.body.estimated_time, 10);
|
|
if (isNaN(time) || time < 0) {
|
|
return res.status(400).json({
|
|
error: 'Estimated time must be a positive number'
|
|
});
|
|
}
|
|
}
|
|
|
|
// ... rest of route handler ...
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
```
|
|
|
|
### Step 6: Update Swagger Documentation
|
|
|
|
Edit `/backend/config/swagger.js`:
|
|
|
|
```javascript
|
|
// Find Task schema
|
|
components: {
|
|
schemas: {
|
|
Task: {
|
|
type: 'object',
|
|
properties: {
|
|
// ... existing properties ...
|
|
estimated_time: {
|
|
type: 'integer',
|
|
description: 'Estimated time in minutes',
|
|
nullable: true,
|
|
example: 30
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 7: Update Frontend TypeScript Interface
|
|
|
|
If TypeScript interface exists, edit `/frontend/entities/Task.ts`:
|
|
|
|
```typescript
|
|
export interface Task {
|
|
// ... existing fields ...
|
|
estimated_time: number | null;
|
|
// ... rest of fields ...
|
|
}
|
|
```
|
|
|
|
### Step 8: Update Frontend Component
|
|
|
|
Edit `/frontend/components/Task/TaskForm.tsx`:
|
|
|
|
```typescript
|
|
// Add input field
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
Estimated Time (minutes)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={task.estimated_time || ''}
|
|
onChange={(e) => updateTask({
|
|
...task,
|
|
estimated_time: e.target.value ? parseInt(e.target.value) : null
|
|
})}
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
|
/>
|
|
</div>
|
|
```
|
|
|
|
### Step 9: Write Tests
|
|
|
|
Add tests in `/backend/tests/integration/tasks/tasks.test.js`:
|
|
|
|
```javascript
|
|
it('should create task with estimated_time', async () => {
|
|
const response = await request(app)
|
|
.post('/api/v1/task')
|
|
.set('Cookie', authCookie)
|
|
.send({
|
|
name: 'Task with estimate',
|
|
estimated_time: 60
|
|
});
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body.estimated_time).toBe(60);
|
|
});
|
|
|
|
it('should reject negative estimated_time', async () => {
|
|
const response = await request(app)
|
|
.post('/api/v1/task')
|
|
.set('Cookie', authCookie)
|
|
.send({
|
|
name: 'Invalid task',
|
|
estimated_time: -10
|
|
});
|
|
|
|
expect(response.status).toBe(400);
|
|
});
|
|
```
|
|
|
|
### Step 10: Run Tests and Checks
|
|
|
|
```bash
|
|
# Run backend tests
|
|
npm run backend:test
|
|
|
|
# Run linting
|
|
npm run lint:fix
|
|
|
|
# Format code
|
|
npm run format:fix
|
|
|
|
# Run all pre-push checks
|
|
npm run pre-push
|
|
```
|
|
|
|
### Step 11: Commit Changes
|
|
|
|
```bash
|
|
git add .
|
|
git commit -m "Add estimated_time field to tasks
|
|
|
|
- Add database migration for estimated_time
|
|
- Update Task model and serializers
|
|
- Update Swagger documentation
|
|
- Add validation for positive values
|
|
- Add UI field in TaskForm component
|
|
- Add integration tests"
|
|
```
|
|
|
|
---
|
|
|
|
## Database Management
|
|
|
|
```bash
|
|
# Reset database (WIPES ALL DATA!)
|
|
npm run db:reset
|
|
|
|
# Seed development data
|
|
npm run db:seed
|
|
|
|
# Check migration status
|
|
npm run db:status
|
|
|
|
# Create new migration
|
|
npm run migration:create -- --name description
|
|
|
|
# Run pending migrations
|
|
npm run migration:run
|
|
|
|
# Rollback last migration
|
|
npm run migration:undo
|
|
```
|
|
|
|
---
|
|
|
|
## Code Quality
|
|
|
|
```bash
|
|
# Check linting
|
|
npm run lint
|
|
|
|
# Auto-fix linting issues
|
|
npm run lint:fix
|
|
|
|
# Format code with Prettier
|
|
npm run format:fix
|
|
|
|
# Run all pre-push checks
|
|
# (lint + format + tests)
|
|
npm run pre-push
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
# Backend tests
|
|
npm test
|
|
# or
|
|
npm run backend:test
|
|
|
|
# Frontend tests
|
|
npm run frontend:test
|
|
|
|
# E2E tests
|
|
npm run test:ui # Headless
|
|
npm run test:ui:headed # With browser visible
|
|
|
|
# Coverage report
|
|
npm run test:coverage
|
|
|
|
# Watch mode (during development)
|
|
npm run test:watch
|
|
```
|
|
|
|
---
|
|
|
|
## Branch Strategy
|
|
|
|
From CONTRIBUTING.md conventions:
|
|
|
|
```bash
|
|
# Feature branches
|
|
git checkout -b feature/description
|
|
|
|
# Bug fix branches
|
|
git checkout -b fix/description
|
|
|
|
# Refactoring branches
|
|
git checkout -b refactor/description
|
|
|
|
# Documentation branches
|
|
git checkout -b docs/description
|
|
|
|
# Test branches
|
|
git checkout -b test/description
|
|
```
|
|
|
|
### Example Workflow
|
|
|
|
```bash
|
|
# Create feature branch from main
|
|
git checkout main
|
|
git pull origin main
|
|
git checkout -b feature/estimated-time
|
|
|
|
# Make changes, run tests
|
|
npm run pre-push
|
|
|
|
# Commit changes
|
|
git add .
|
|
git commit -m "Add estimated time feature"
|
|
|
|
# Before PR: rebase on main
|
|
git checkout main
|
|
git pull origin main
|
|
git checkout feature/estimated-time
|
|
git rebase main
|
|
|
|
# Push and create PR
|
|
git push origin feature/estimated-time
|
|
```
|
|
|
|
---
|
|
|
|
## Build for Production
|
|
|
|
```bash
|
|
# Build frontend
|
|
npm run build
|
|
|
|
# Builds to: /dist/
|
|
# - Minified JavaScript
|
|
# - Optimized CSS
|
|
# - Hashed filenames for cache busting
|
|
```
|
|
|
|
### Docker Production
|
|
|
|
```bash
|
|
# Build Docker image
|
|
docker build -t tududi:latest .
|
|
|
|
# Run container
|
|
docker run \
|
|
-e TUDUDI_USER_EMAIL=admin@example.com \
|
|
-e TUDUDI_USER_PASSWORD=secure-password \
|
|
-e TUDUDI_SESSION_SECRET=$(openssl rand -hex 64) \
|
|
-v ~/tududi_db:/app/backend/db \
|
|
-v ~/tududi_uploads:/app/backend/uploads \
|
|
-p 3002:3002 \
|
|
-d tududi:latest
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Port Already in Use
|
|
|
|
```bash
|
|
# Find process using port 3002
|
|
lsof -ti:3002
|
|
|
|
# Kill process
|
|
kill -9 $(lsof -ti:3002)
|
|
|
|
# Or use different port
|
|
PORT=3003 npm run backend:dev
|
|
```
|
|
|
|
### Database Locked
|
|
|
|
```bash
|
|
# Stop all servers
|
|
# Delete database file
|
|
rm backend/database.sqlite
|
|
|
|
# Reinitialize
|
|
npm run db:init
|
|
```
|
|
|
|
### Module Not Found
|
|
|
|
```bash
|
|
# Clean install
|
|
rm -rf node_modules package-lock.json
|
|
npm install
|
|
```
|
|
|
|
### Webpack Build Errors
|
|
|
|
```bash
|
|
# Clear webpack cache
|
|
rm -rf node_modules/.cache
|
|
|
|
# Rebuild
|
|
npm run frontend:dev
|
|
```
|
|
|
|
---
|
|
|
|
[← Back to Index](../CLAUDE.md)
|