add changes
This commit is contained in:
672
SAMPLE_MIGRATIONS.md
Normal file
672
SAMPLE_MIGRATIONS.md
Normal file
@@ -0,0 +1,672 @@
|
||||
# Sample Sequelize Migration Files
|
||||
|
||||
## Migration 1: Create Users Table
|
||||
|
||||
**File**: `migrations/20250101000001-create-users.js`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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`
|
||||
|
||||
```javascript
|
||||
'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
|
||||
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```javascript
|
||||
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`
|
||||
|
||||
```javascript
|
||||
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. 🚀
|
||||
Reference in New Issue
Block a user