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

10 KiB

Development Workflow

← Back to Index


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

← Back to Index