add changes
This commit is contained in:
641
SEQUELIZE_QUICK_REFERENCE.md
Normal file
641
SEQUELIZE_QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,641 @@
|
||||
# Sequelize Quick Reference Guide
|
||||
|
||||
## Common Operations for Interview Quiz App
|
||||
|
||||
### 1. Database Connection
|
||||
|
||||
```javascript
|
||||
// config/database.js
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME,
|
||||
process.env.DB_USER,
|
||||
process.env.DB_PASSWORD,
|
||||
{
|
||||
host: process.env.DB_HOST,
|
||||
dialect: 'mysql',
|
||||
pool: { max: 10, min: 0, acquire: 30000, idle: 10000 }
|
||||
}
|
||||
);
|
||||
|
||||
// Test connection
|
||||
sequelize.authenticate()
|
||||
.then(() => console.log('MySQL connected'))
|
||||
.catch(err => console.error('Unable to connect:', err));
|
||||
|
||||
module.exports = sequelize;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. User Operations
|
||||
|
||||
#### Register New User
|
||||
```javascript
|
||||
const bcrypt = require('bcrypt');
|
||||
const { User } = require('../models');
|
||||
|
||||
const registerUser = async (userData) => {
|
||||
const hashedPassword = await bcrypt.hash(userData.password, 10);
|
||||
|
||||
const user = await User.create({
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
password: hashedPassword,
|
||||
role: 'user'
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
```
|
||||
|
||||
#### Login User
|
||||
```javascript
|
||||
const loginUser = async (email, password) => {
|
||||
const user = await User.findOne({
|
||||
where: { email },
|
||||
attributes: ['id', 'username', 'email', 'password', 'role']
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
```
|
||||
|
||||
#### Get User Dashboard
|
||||
```javascript
|
||||
const getUserDashboard = async (userId) => {
|
||||
const user = await User.findByPk(userId, {
|
||||
attributes: [
|
||||
'id', 'username', 'email',
|
||||
'totalQuizzes', 'totalQuestions', 'correctAnswers', 'streak'
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: QuizSession,
|
||||
where: { status: 'completed' },
|
||||
required: false,
|
||||
limit: 10,
|
||||
order: [['completedAt', 'DESC']],
|
||||
include: [{ model: Category, attributes: ['name'] }]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Category Operations
|
||||
|
||||
#### Get All Categories
|
||||
```javascript
|
||||
const getCategories = async () => {
|
||||
const categories = await Category.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: {
|
||||
include: [
|
||||
[
|
||||
sequelize.literal(`(
|
||||
SELECT COUNT(*)
|
||||
FROM questions
|
||||
WHERE questions.category_id = categories.id
|
||||
AND questions.is_active = true
|
||||
)`),
|
||||
'questionCount'
|
||||
]
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
return categories;
|
||||
};
|
||||
```
|
||||
|
||||
#### Get Guest-Accessible Categories
|
||||
```javascript
|
||||
const getGuestCategories = async () => {
|
||||
const categories = await Category.findAll({
|
||||
where: {
|
||||
isActive: true,
|
||||
guestAccessible: true
|
||||
},
|
||||
attributes: ['id', 'name', 'description', 'icon', 'publicQuestionCount']
|
||||
});
|
||||
|
||||
return categories;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Question Operations
|
||||
|
||||
#### Create Question (Admin)
|
||||
```javascript
|
||||
const createQuestion = async (questionData) => {
|
||||
const question = await Question.create({
|
||||
question: questionData.question,
|
||||
type: questionData.type,
|
||||
categoryId: questionData.categoryId,
|
||||
difficulty: questionData.difficulty,
|
||||
options: JSON.stringify(questionData.options), // Store as JSON
|
||||
correctAnswer: questionData.correctAnswer,
|
||||
explanation: questionData.explanation,
|
||||
keywords: JSON.stringify(questionData.keywords),
|
||||
tags: JSON.stringify(questionData.tags),
|
||||
visibility: questionData.visibility || 'registered',
|
||||
isGuestAccessible: questionData.isGuestAccessible || false,
|
||||
createdBy: questionData.userId
|
||||
});
|
||||
|
||||
// Update category question count
|
||||
await Category.increment('questionCount', {
|
||||
where: { id: questionData.categoryId }
|
||||
});
|
||||
|
||||
return question;
|
||||
};
|
||||
```
|
||||
|
||||
#### Get Questions by Category
|
||||
```javascript
|
||||
const getQuestionsByCategory = async (categoryId, options = {}) => {
|
||||
const { difficulty, limit = 10, visibility = 'registered' } = options;
|
||||
|
||||
const where = {
|
||||
categoryId,
|
||||
isActive: true,
|
||||
visibility: {
|
||||
[Op.in]: visibility === 'guest' ? ['public'] : ['public', 'registered', 'premium']
|
||||
}
|
||||
};
|
||||
|
||||
if (difficulty) {
|
||||
where.difficulty = difficulty;
|
||||
}
|
||||
|
||||
const questions = await Question.findAll({
|
||||
where,
|
||||
order: sequelize.random(),
|
||||
limit,
|
||||
include: [{ model: Category, attributes: ['name'] }]
|
||||
});
|
||||
|
||||
return questions;
|
||||
};
|
||||
```
|
||||
|
||||
#### Search Questions (Full-Text)
|
||||
```javascript
|
||||
const searchQuestions = async (searchTerm) => {
|
||||
const questions = await Question.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
sequelize.literal(`MATCH(question, explanation) AGAINST('${searchTerm}' IN NATURAL LANGUAGE MODE)`)
|
||||
],
|
||||
isActive: true
|
||||
},
|
||||
limit: 50
|
||||
});
|
||||
|
||||
return questions;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Quiz Session Operations
|
||||
|
||||
#### Start Quiz Session
|
||||
```javascript
|
||||
const startQuizSession = async (sessionData) => {
|
||||
const { userId, guestSessionId, categoryId, questionCount, difficulty } = sessionData;
|
||||
|
||||
// Create quiz session
|
||||
const quizSession = await QuizSession.create({
|
||||
userId: userId || null,
|
||||
guestSessionId: guestSessionId || null,
|
||||
isGuestSession: !userId,
|
||||
categoryId,
|
||||
totalQuestions: questionCount,
|
||||
status: 'in-progress',
|
||||
startTime: new Date()
|
||||
});
|
||||
|
||||
// Get random questions
|
||||
const questions = await Question.findAll({
|
||||
where: {
|
||||
categoryId,
|
||||
difficulty: difficulty || { [Op.in]: ['easy', 'medium', 'hard'] },
|
||||
isActive: true
|
||||
},
|
||||
order: sequelize.random(),
|
||||
limit: questionCount
|
||||
});
|
||||
|
||||
// Create junction entries
|
||||
const sessionQuestions = questions.map((q, index) => ({
|
||||
quizSessionId: quizSession.id,
|
||||
questionId: q.id,
|
||||
questionOrder: index + 1
|
||||
}));
|
||||
|
||||
await sequelize.models.QuizSessionQuestion.bulkCreate(sessionQuestions);
|
||||
|
||||
return { quizSession, questions };
|
||||
};
|
||||
```
|
||||
|
||||
#### Submit Answer
|
||||
```javascript
|
||||
const submitAnswer = async (answerData) => {
|
||||
const { quizSessionId, questionId, userAnswer } = answerData;
|
||||
|
||||
// Get question
|
||||
const question = await Question.findByPk(questionId);
|
||||
|
||||
// Check if correct
|
||||
const isCorrect = question.correctAnswer === userAnswer;
|
||||
|
||||
// Save answer
|
||||
const answer = await QuizAnswer.create({
|
||||
quizSessionId,
|
||||
questionId,
|
||||
userAnswer,
|
||||
isCorrect,
|
||||
timeSpent: answerData.timeSpent || 0
|
||||
});
|
||||
|
||||
// Update quiz session score
|
||||
if (isCorrect) {
|
||||
await QuizSession.increment('score', { where: { id: quizSessionId } });
|
||||
}
|
||||
|
||||
// Update question statistics
|
||||
await Question.increment('timesAttempted', { where: { id: questionId } });
|
||||
|
||||
return {
|
||||
isCorrect,
|
||||
correctAnswer: question.correctAnswer,
|
||||
explanation: question.explanation
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### Complete Quiz Session
|
||||
```javascript
|
||||
const completeQuizSession = async (sessionId) => {
|
||||
const session = await QuizSession.findByPk(sessionId, {
|
||||
include: [
|
||||
{
|
||||
model: QuizAnswer,
|
||||
include: [{ model: Question, attributes: ['id', 'question'] }]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Calculate results
|
||||
const correctAnswers = session.QuizAnswers.filter(a => a.isCorrect).length;
|
||||
const percentage = (correctAnswers / session.totalQuestions) * 100;
|
||||
const timeTaken = Math.floor((new Date() - session.startTime) / 1000); // seconds
|
||||
|
||||
// Update session
|
||||
await session.update({
|
||||
status: 'completed',
|
||||
endTime: new Date(),
|
||||
completedAt: new Date()
|
||||
});
|
||||
|
||||
// Update user stats if not guest
|
||||
if (session.userId) {
|
||||
await User.increment({
|
||||
totalQuizzes: 1,
|
||||
totalQuestions: session.totalQuestions,
|
||||
correctAnswers: correctAnswers
|
||||
}, { where: { id: session.userId } });
|
||||
}
|
||||
|
||||
return {
|
||||
score: session.score,
|
||||
totalQuestions: session.totalQuestions,
|
||||
percentage,
|
||||
timeTaken,
|
||||
correctAnswers,
|
||||
incorrectAnswers: session.totalQuestions - correctAnswers,
|
||||
results: session.QuizAnswers
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Guest Session Operations
|
||||
|
||||
#### Create Guest Session
|
||||
```javascript
|
||||
const createGuestSession = async (deviceId, ipAddress, userAgent) => {
|
||||
const guestId = `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const sessionToken = jwt.sign({ guestId }, process.env.JWT_SECRET, { expiresIn: '24h' });
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
||||
|
||||
const guestSession = await GuestSession.create({
|
||||
guestId,
|
||||
deviceId,
|
||||
sessionToken,
|
||||
quizzesAttempted: 0,
|
||||
maxQuizzes: 3,
|
||||
expiresAt,
|
||||
ipAddress,
|
||||
userAgent
|
||||
});
|
||||
|
||||
return guestSession;
|
||||
};
|
||||
```
|
||||
|
||||
#### Check Guest Quiz Limit
|
||||
```javascript
|
||||
const checkGuestQuizLimit = async (guestSessionId) => {
|
||||
const session = await GuestSession.findByPk(guestSessionId);
|
||||
|
||||
if (!session) {
|
||||
throw new Error('Guest session not found');
|
||||
}
|
||||
|
||||
if (new Date() > session.expiresAt) {
|
||||
throw new Error('Guest session expired');
|
||||
}
|
||||
|
||||
const remainingQuizzes = session.maxQuizzes - session.quizzesAttempted;
|
||||
|
||||
if (remainingQuizzes <= 0) {
|
||||
throw new Error('Guest quiz limit reached');
|
||||
}
|
||||
|
||||
return {
|
||||
remainingQuizzes,
|
||||
resetTime: session.expiresAt
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Bookmark Operations
|
||||
|
||||
#### Add Bookmark
|
||||
```javascript
|
||||
const addBookmark = async (userId, questionId) => {
|
||||
const bookmark = await sequelize.models.UserBookmark.create({
|
||||
userId,
|
||||
questionId
|
||||
});
|
||||
|
||||
return bookmark;
|
||||
};
|
||||
```
|
||||
|
||||
#### Get User Bookmarks
|
||||
```javascript
|
||||
const getUserBookmarks = async (userId) => {
|
||||
const user = await User.findByPk(userId, {
|
||||
include: [{
|
||||
model: Question,
|
||||
as: 'bookmarks',
|
||||
through: { attributes: ['bookmarkedAt'] },
|
||||
include: [{ model: Category, attributes: ['name'] }]
|
||||
}]
|
||||
});
|
||||
|
||||
return user.bookmarks;
|
||||
};
|
||||
```
|
||||
|
||||
#### Remove Bookmark
|
||||
```javascript
|
||||
const removeBookmark = async (userId, questionId) => {
|
||||
await sequelize.models.UserBookmark.destroy({
|
||||
where: { userId, questionId }
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. Admin Operations
|
||||
|
||||
#### Get System Statistics
|
||||
```javascript
|
||||
const getSystemStats = async () => {
|
||||
const [totalUsers, activeUsers, totalQuizzes] = await Promise.all([
|
||||
User.count(),
|
||||
User.count({
|
||||
where: {
|
||||
lastLogin: { [Op.gte]: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
|
||||
}
|
||||
}),
|
||||
QuizSession.count({ where: { status: 'completed' } })
|
||||
]);
|
||||
|
||||
const popularCategories = await Category.findAll({
|
||||
attributes: [
|
||||
'id', 'name',
|
||||
[sequelize.fn('COUNT', sequelize.col('QuizSessions.id')), 'quizCount']
|
||||
],
|
||||
include: [{
|
||||
model: QuizSession,
|
||||
attributes: [],
|
||||
required: false
|
||||
}],
|
||||
group: ['Category.id'],
|
||||
order: [[sequelize.literal('quizCount'), 'DESC']],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
const avgScore = await QuizSession.findOne({
|
||||
attributes: [[sequelize.fn('AVG', sequelize.col('score')), 'avgScore']],
|
||||
where: { status: 'completed' }
|
||||
});
|
||||
|
||||
return {
|
||||
totalUsers,
|
||||
activeUsers,
|
||||
totalQuizzes,
|
||||
popularCategories,
|
||||
averageScore: avgScore.dataValues.avgScore || 0
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### Update Guest Settings
|
||||
```javascript
|
||||
const updateGuestSettings = async (settings, adminUserId) => {
|
||||
const guestSettings = await GuestSettings.findOne();
|
||||
|
||||
if (guestSettings) {
|
||||
await guestSettings.update({
|
||||
...settings,
|
||||
updatedBy: adminUserId
|
||||
});
|
||||
} else {
|
||||
await GuestSettings.create({
|
||||
...settings,
|
||||
updatedBy: adminUserId
|
||||
});
|
||||
}
|
||||
|
||||
return guestSettings;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. Transaction Example
|
||||
|
||||
```javascript
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
const convertGuestToUser = async (guestSessionId, userData) => {
|
||||
const t = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
// Create user
|
||||
const user = await User.create(userData, { transaction: t });
|
||||
|
||||
// Get guest session
|
||||
const guestSession = await GuestSession.findByPk(guestSessionId, { transaction: t });
|
||||
|
||||
// Migrate quiz sessions
|
||||
await QuizSession.update(
|
||||
{ userId: user.id, isGuestSession: false },
|
||||
{ where: { guestSessionId }, transaction: t }
|
||||
);
|
||||
|
||||
// Calculate stats
|
||||
const stats = await QuizSession.findAll({
|
||||
where: { userId: user.id },
|
||||
attributes: [
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'totalQuizzes'],
|
||||
[sequelize.fn('SUM', sequelize.col('score')), 'totalScore']
|
||||
],
|
||||
transaction: t
|
||||
});
|
||||
|
||||
// Update user stats
|
||||
await user.update({
|
||||
totalQuizzes: stats[0].dataValues.totalQuizzes || 0
|
||||
}, { transaction: t });
|
||||
|
||||
// Delete guest session
|
||||
await guestSession.destroy({ transaction: t });
|
||||
|
||||
await t.commit();
|
||||
return user;
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. Common Query Operators
|
||||
|
||||
```javascript
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Examples of common operators
|
||||
const examples = {
|
||||
// Equals
|
||||
{ status: 'completed' },
|
||||
|
||||
// Not equals
|
||||
{ status: { [Op.ne]: 'abandoned' } },
|
||||
|
||||
// Greater than / Less than
|
||||
{ score: { [Op.gte]: 80 } },
|
||||
{ createdAt: { [Op.lte]: new Date() } },
|
||||
|
||||
// Between
|
||||
{ score: { [Op.between]: [50, 100] } },
|
||||
|
||||
// In array
|
||||
{ difficulty: { [Op.in]: ['easy', 'medium'] } },
|
||||
|
||||
// Like (case-sensitive)
|
||||
{ question: { [Op.like]: '%javascript%' } },
|
||||
|
||||
// Not null
|
||||
{ userId: { [Op.not]: null } },
|
||||
|
||||
// OR condition
|
||||
{
|
||||
[Op.or]: [
|
||||
{ visibility: 'public' },
|
||||
{ isGuestAccessible: true }
|
||||
]
|
||||
},
|
||||
|
||||
// AND condition
|
||||
{
|
||||
[Op.and]: [
|
||||
{ isActive: true },
|
||||
{ difficulty: 'hard' }
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
```javascript
|
||||
const handleSequelizeError = (error) => {
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
return { status: 400, message: 'Record already exists' };
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeValidationError') {
|
||||
return { status: 400, message: error.errors.map(e => e.message).join(', ') };
|
||||
}
|
||||
|
||||
if (error.name === 'SequelizeForeignKeyConstraintError') {
|
||||
return { status: 400, message: 'Invalid foreign key reference' };
|
||||
}
|
||||
|
||||
return { status: 500, message: 'Database error' };
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use parameterized queries** (Sequelize does this by default)
|
||||
2. **Use transactions for multi-step operations**
|
||||
3. **Index frequently queried columns**
|
||||
4. **Use eager loading to avoid N+1 queries**
|
||||
5. **Limit result sets with pagination**
|
||||
6. **Use connection pooling**
|
||||
7. **Handle errors gracefully**
|
||||
8. **Log slow queries in development**
|
||||
9. **Use prepared statements**
|
||||
10. **Validate input before database operations**
|
||||
|
||||
---
|
||||
|
||||
Happy coding! 🚀
|
||||
Reference in New Issue
Block a user