# 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