tududi/docs/development-workflow.md
Chris ea78e81321
feat: add configurable file upload limit via environment variable (#1080)
* 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
2026-04-27 13:35:02 +03:00

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)