* 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
10 KiB
Development Workflow
Initial Setup
Prerequisites
- Node.js v22+ (recommended - check package.json engines field)
- npm (comes with Node.js)
- Git
Clone and Install
# 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
# 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)
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):
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):
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:
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):
# 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:
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
npm run migration:create -- --name add-estimated-time-to-tasks
Edit the created file /backend/migrations/YYYYMMDDHHMMSS-add-estimated-time-to-tasks.js:
'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:
npm run migration:run
Step 2: Update Sequelize Model
Edit /backend/models/task.js:
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:
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:
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:
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:
// 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:
export interface Task {
// ... existing fields ...
estimated_time: number | null;
// ... rest of fields ...
}
Step 8: Update Frontend Component
Edit /frontend/components/Task/TaskForm.tsx:
// 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:
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
# 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
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
# 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
# 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
# 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:
# 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
# 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
# Build frontend
npm run build
# Builds to: /dist/
# - Minified JavaScript
# - Optimized CSS
# - Hashed filenames for cache busting
Docker Production
# 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
# 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
# Stop all servers
# Delete database file
rm backend/database.sqlite
# Reinitialize
npm run db:init
Module Not Found
# Clean install
rm -rf node_modules package-lock.json
npm install
Webpack Build Errors
# Clear webpack cache
rm -rf node_modules/.cache
# Rebuild
npm run frontend:dev