add changes

This commit is contained in:
AD2025
2025-11-11 00:25:50 +02:00
commit e3ca132c5e
86 changed files with 22238 additions and 0 deletions

672
SAMPLE_MIGRATIONS.md Normal file
View 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. 🚀