Files
Tasks/SAMPLE_MIGRATIONS.md
2025-11-11 00:25:50 +02:00

16 KiB

Sample Sequelize Migration Files

Migration 1: Create Users Table

File: migrations/20250101000001-create-users.js

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('users', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      username: {
        type: Sequelize.STRING(50),
        unique: true,
        allowNull: false
      },
      email: {
        type: Sequelize.STRING(255),
        unique: true,
        allowNull: false
      },
      password: {
        type: Sequelize.STRING(255),
        allowNull: false
      },
      role: {
        type: Sequelize.ENUM('user', 'admin'),
        defaultValue: 'user'
      },
      profile_image: {
        type: Sequelize.STRING(500)
      },
      created_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      updated_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
      },
      last_login: {
        type: Sequelize.DATE,
        allowNull: true
      },
      total_quizzes: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      total_questions: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      correct_answers: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      streak: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      }
    });

    // Add indexes
    await queryInterface.addIndex('users', ['email']);
    await queryInterface.addIndex('users', ['username']);
    await queryInterface.addIndex('users', ['role']);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('users');
  }
};

Migration 2: Create Categories Table

File: migrations/20250101000002-create-categories.js

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('categories', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      name: {
        type: Sequelize.STRING(100),
        unique: true,
        allowNull: false
      },
      description: {
        type: Sequelize.TEXT
      },
      icon: {
        type: Sequelize.STRING(255)
      },
      slug: {
        type: Sequelize.STRING(100),
        unique: true,
        allowNull: false
      },
      question_count: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      is_active: {
        type: Sequelize.BOOLEAN,
        defaultValue: true
      },
      guest_accessible: {
        type: Sequelize.BOOLEAN,
        defaultValue: false
      },
      public_question_count: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      registered_question_count: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      created_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      updated_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
      }
    });

    // Add indexes
    await queryInterface.addIndex('categories', ['slug']);
    await queryInterface.addIndex('categories', ['is_active']);
    await queryInterface.addIndex('categories', ['guest_accessible']);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('categories');
  }
};

Migration 3: Create Questions Table

File: migrations/20250101000003-create-questions.js

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('questions', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      question: {
        type: Sequelize.TEXT,
        allowNull: false
      },
      type: {
        type: Sequelize.ENUM('multiple', 'trueFalse', 'written'),
        allowNull: false
      },
      category_id: {
        type: Sequelize.UUID,
        allowNull: false,
        references: {
          model: 'categories',
          key: 'id'
        },
        onUpdate: 'CASCADE',
        onDelete: 'RESTRICT'
      },
      difficulty: {
        type: Sequelize.ENUM('easy', 'medium', 'hard'),
        allowNull: false
      },
      options: {
        type: Sequelize.JSON,
        comment: 'Array of answer options for multiple choice questions'
      },
      correct_answer: {
        type: Sequelize.STRING(500)
      },
      explanation: {
        type: Sequelize.TEXT,
        allowNull: false
      },
      keywords: {
        type: Sequelize.JSON,
        comment: 'Array of keywords for search'
      },
      tags: {
        type: Sequelize.JSON,
        comment: 'Array of tags'
      },
      visibility: {
        type: Sequelize.ENUM('public', 'registered', 'premium'),
        defaultValue: 'registered'
      },
      is_guest_accessible: {
        type: Sequelize.BOOLEAN,
        defaultValue: false
      },
      created_by: {
        type: Sequelize.UUID,
        references: {
          model: 'users',
          key: 'id'
        },
        onUpdate: 'CASCADE',
        onDelete: 'SET NULL'
      },
      created_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      updated_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
      },
      is_active: {
        type: Sequelize.BOOLEAN,
        defaultValue: true
      },
      times_attempted: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      correct_rate: {
        type: Sequelize.DECIMAL(5, 2),
        defaultValue: 0.00
      }
    });

    // Add indexes
    await queryInterface.addIndex('questions', ['category_id']);
    await queryInterface.addIndex('questions', ['difficulty']);
    await queryInterface.addIndex('questions', ['type']);
    await queryInterface.addIndex('questions', ['visibility']);
    await queryInterface.addIndex('questions', ['is_active']);
    await queryInterface.addIndex('questions', ['is_guest_accessible']);
    
    // Add full-text index
    await queryInterface.sequelize.query(
      'CREATE FULLTEXT INDEX idx_questions_fulltext ON questions(question, explanation)'
    );
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('questions');
  }
};

Migration 4: Create Guest Sessions Table

File: migrations/20250101000004-create-guest-sessions.js

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('guest_sessions', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      guest_id: {
        type: Sequelize.STRING(100),
        unique: true,
        allowNull: false
      },
      device_id: {
        type: Sequelize.STRING(255),
        allowNull: false
      },
      session_token: {
        type: Sequelize.STRING(500),
        allowNull: false
      },
      quizzes_attempted: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      max_quizzes: {
        type: Sequelize.INTEGER,
        defaultValue: 3
      },
      created_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      expires_at: {
        type: Sequelize.DATE,
        allowNull: false
      },
      ip_address: {
        type: Sequelize.STRING(45)
      },
      user_agent: {
        type: Sequelize.TEXT
      }
    });

    // Add indexes
    await queryInterface.addIndex('guest_sessions', ['guest_id']);
    await queryInterface.addIndex('guest_sessions', ['session_token']);
    await queryInterface.addIndex('guest_sessions', ['expires_at']);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('guest_sessions');
  }
};

Migration 5: Create Quiz Sessions Table

File: migrations/20250101000005-create-quiz-sessions.js

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('quiz_sessions', {
      id: {
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4,
        primaryKey: true
      },
      user_id: {
        type: Sequelize.UUID,
        references: {
          model: 'users',
          key: 'id'
        },
        onUpdate: 'CASCADE',
        onDelete: 'CASCADE'
      },
      guest_session_id: {
        type: Sequelize.UUID,
        references: {
          model: 'guest_sessions',
          key: 'id'
        },
        onUpdate: 'CASCADE',
        onDelete: 'CASCADE'
      },
      is_guest_session: {
        type: Sequelize.BOOLEAN,
        defaultValue: false
      },
      category_id: {
        type: Sequelize.UUID,
        references: {
          model: 'categories',
          key: 'id'
        },
        onUpdate: 'CASCADE',
        onDelete: 'SET NULL'
      },
      start_time: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      end_time: {
        type: Sequelize.DATE,
        allowNull: true
      },
      score: {
        type: Sequelize.INTEGER,
        defaultValue: 0
      },
      total_questions: {
        type: Sequelize.INTEGER,
        allowNull: false
      },
      status: {
        type: Sequelize.ENUM('in-progress', 'completed', 'abandoned'),
        defaultValue: 'in-progress'
      },
      completed_at: {
        type: Sequelize.DATE,
        allowNull: true
      },
      created_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
      },
      updated_at: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
      }
    });

    // Add indexes
    await queryInterface.addIndex('quiz_sessions', ['user_id']);
    await queryInterface.addIndex('quiz_sessions', ['guest_session_id']);
    await queryInterface.addIndex('quiz_sessions', ['status']);
    await queryInterface.addIndex('quiz_sessions', ['completed_at']);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('quiz_sessions');
  }
};

Seeder Example: Demo Categories

File: seeders/20250101000001-demo-categories.js

'use strict';
const { v4: uuidv4 } = require('uuid');

module.exports = {
  up: async (queryInterface, Sequelize) => {
    const categories = [
      {
        id: uuidv4(),
        name: 'Angular',
        description: 'Frontend framework by Google',
        icon: 'angular-icon.svg',
        slug: 'angular',
        question_count: 0,
        is_active: true,
        guest_accessible: true,
        public_question_count: 0,
        registered_question_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      },
      {
        id: uuidv4(),
        name: 'Node.js',
        description: 'JavaScript runtime for backend',
        icon: 'nodejs-icon.svg',
        slug: 'nodejs',
        question_count: 0,
        is_active: true,
        guest_accessible: true,
        public_question_count: 0,
        registered_question_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      },
      {
        id: uuidv4(),
        name: 'MySQL',
        description: 'Relational database management system',
        icon: 'mysql-icon.svg',
        slug: 'mysql',
        question_count: 0,
        is_active: true,
        guest_accessible: false,
        public_question_count: 0,
        registered_question_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      },
      {
        id: uuidv4(),
        name: 'Express.js',
        description: 'Web framework for Node.js',
        icon: 'express-icon.svg',
        slug: 'expressjs',
        question_count: 0,
        is_active: true,
        guest_accessible: true,
        public_question_count: 0,
        registered_question_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      },
      {
        id: uuidv4(),
        name: 'JavaScript',
        description: 'Core programming language',
        icon: 'javascript-icon.svg',
        slug: 'javascript',
        question_count: 0,
        is_active: true,
        guest_accessible: true,
        public_question_count: 0,
        registered_question_count: 0,
        created_at: new Date(),
        updated_at: new Date()
      }
    ];

    await queryInterface.bulkInsert('categories', categories);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('categories', null, {});
  }
};

Seeder Example: Demo Admin User

File: seeders/20250101000002-demo-admin.js

'use strict';
const bcrypt = require('bcrypt');
const { v4: uuidv4 } = require('uuid');

module.exports = {
  up: async (queryInterface, Sequelize) => {
    const hashedPassword = await bcrypt.hash('Admin@123', 10);
    
    await queryInterface.bulkInsert('users', [{
      id: uuidv4(),
      username: 'admin',
      email: 'admin@quizapp.com',
      password: hashedPassword,
      role: 'admin',
      profile_image: null,
      created_at: new Date(),
      updated_at: new Date(),
      last_login: null,
      total_quizzes: 0,
      total_questions: 0,
      correct_answers: 0,
      streak: 0
    }]);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('users', { email: 'admin@quizapp.com' }, {});
  }
};

Running Migrations

# Create database first
mysql -u root -p
CREATE DATABASE interview_quiz_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
EXIT;

# Run all migrations
npx sequelize-cli db:migrate

# Check migration status
npx sequelize-cli db:migrate:status

# Undo last migration
npx sequelize-cli db:migrate:undo

# Undo all migrations
npx sequelize-cli db:migrate:undo:all

# Run seeders
npx sequelize-cli db:seed:all

# Undo seeders
npx sequelize-cli db:seed:undo:all

.sequelizerc Configuration

File: .sequelizerc (in project root)

const path = require('path');

module.exports = {
  'config': path.resolve('config', 'database.js'),
  'models-path': path.resolve('models'),
  'seeders-path': path.resolve('seeders'),
  'migrations-path': path.resolve('migrations')
};

Database Configuration

File: config/database.js

require('dotenv').config();

module.exports = {
  development: {
    username: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || '',
    database: process.env.DB_NAME || 'interview_quiz_db',
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 3306,
    dialect: 'mysql',
    logging: console.log,
    pool: {
      max: 10,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
  },
  test: {
    username: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || '',
    database: 'interview_quiz_test',
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 3306,
    dialect: 'mysql',
    logging: false
  },
  production: {
    username: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    port: process.env.DB_PORT || 3306,
    dialect: 'mysql',
    logging: false,
    pool: {
      max: 20,
      min: 5,
      acquire: 30000,
      idle: 10000
    },
    dialectOptions: {
      ssl: {
        require: true,
        rejectUnauthorized: false
      }
    }
  }
};

That's it! Your migration files are ready to use. 🚀