Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions backend/controllers/questionController.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
const QuestionSet = require('../models/questions');

exports.createQuestionSet = async (req, res) => {
const { folder_name, tag_name, questions } = req.body;
const userId = req.user.id; // From auth middleware
const { folder_name, tag_name, questions, user_id } = req.body;
const userId = user_id || (req.user && req.user.id); // Support both ways

// Basic validation
if (!folder_name || !tag_name || !questions || !Array.isArray(questions)) {
return res.status(400).json({ error: 'Invalid request payload' });
}

if (!userId) {
return res.status(400).json({ error: 'user_id is required when not authenticated' });
}

// Validate each question
for (const question of questions) {
if (!question.description || !question.options || !question.answer || !question.user_answer) {
Expand All @@ -31,10 +35,71 @@ exports.createQuestionSet = async (req, res) => {
const result = await QuestionSet.createQuestionSet(folder_name, tag_name, questions, userId);
res.status(201).json({
message: 'Question set created successfully',
data: result
data: result,
summary: {
folder: result.isNewFolder ? `Created new folder: ${folder_name}` : `Used existing folder: ${folder_name}`,
tag: result.isNewTag ? `Created new tag: ${tag_name}` : `Used existing tag: ${tag_name}`,
questions_added: questions.length
}
});
} catch (err) {
console.error('Error creating question set:', err);
res.status(500).json({ error: 'Failed to create question set' });
}
};

exports.getAllQuestions = async (req, res) => {
const { user_id } = req.params;

// Basic validation
if (!user_id) {
return res.status(400).json({ error: 'user_id is required in URL parameter' });
}

try {
console.log('Fetching questions for user_id:', user_id);
const folders = await QuestionSet.getAllQuestionsByUserId(user_id);
console.log('Retrieved folders:', folders);

// Calculate total questions count
const totalQuestions = folders.reduce((total, folder) => total + folder.questions.length, 0);

res.status(200).json({
message: 'Questions retrieved successfully',
data: {
folders,
total_folders: folders.length,
total_questions: totalQuestions
}
});
} catch (err) {
console.error('Error retrieving questions:', err);
res.status(500).json({ error: 'Failed to retrieve questions' });
}
};

exports.getAllTags = async (req, res) => {
const { user_id } = req.params;

// Basic validation
if (!user_id) {
return res.status(400).json({ error: 'user_id is required in URL parameter' });
}

try {
console.log('Fetching tags for user_id:', user_id);
const tags = await QuestionSet.getAllTagsByUserId(user_id);
console.log('Retrieved tags:', tags);

res.status(200).json({
message: 'Tags retrieved successfully',
data: {
tags,
total_tags: tags.length
}
});
} catch (err) {
console.error('Error retrieving tags:', err);
res.status(500).json({ error: 'Failed to retrieve tags' });
}
};
1 change: 1 addition & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,5 @@ app.listen(PORT, '0.0.0.0', (err) => {
console.log('- GET /auth/me');
console.log('- GET /api/test');
console.log('- POST /api/question-set');
console.log('- POST /api/get-all-questions');
});
140 changes: 134 additions & 6 deletions backend/models/questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ class QuestionSet {
return result.rows[0];
}

static async findFolderByName(title, userId) {
const query = `
SELECT * FROM folders
WHERE title = $1 AND user_id = $2
LIMIT 1
`;
const result = await db.query(query, [title, userId]);
return result.rows[0] || null;
}

static async createTag(name, userId) {
const query = `
INSERT INTO tags (name, user_id)
Expand All @@ -21,7 +31,34 @@ class QuestionSet {
return result.rows[0];
}

static async findTagByName(name, userId) {
const query = `
SELECT * FROM tags
WHERE name = $1 AND user_id = $2
LIMIT 1
`;
const result = await db.query(query, [name, userId]);
return result.rows[0] || null;
}

static async createQuestion(folderId, description, options, answer, userAnswer, note) {
// For JSONB columns, PostgreSQL handles JSON conversion automatically
// We should pass the object directly, not stringify it
let processedOptions;

if (typeof options === 'object' && options !== null) {
processedOptions = options;
} else if (typeof options === 'string') {
try {
processedOptions = JSON.parse(options);
} catch (error) {
console.warn('Invalid JSON string for options, using empty object:', error.message);
processedOptions = {};
}
} else {
processedOptions = {};
}

const query = `
INSERT INTO questions (folder_id, description, options, answer, user_answer, note)
VALUES ($1, $2, $3, $4, $5, $6)
Expand All @@ -30,7 +67,7 @@ class QuestionSet {
const result = await db.query(query, [
folderId,
description,
JSON.stringify(options),
processedOptions, // Pass object directly for JSONB
answer,
userAnswer,
note
Expand All @@ -43,11 +80,25 @@ class QuestionSet {
try {
await client.query('BEGIN');

// Create tag
const tag = await this.createTag(tagName, userId);
// Check if tag exists, if not create it
let tag = await this.findTagByName(tagName, userId);
const isNewTag = !tag;
if (!tag) {
tag = await this.createTag(tagName, userId);
console.log(`Created new tag: ${tagName}`);
} else {
console.log(`Using existing tag: ${tagName}`);
}

// Create folder
const folder = await this.createFolder(folderName, tag.id, userId);
// Check if folder exists, if not create it
let folder = await this.findFolderByName(folderName, userId);
const isNewFolder = !folder;
if (!folder) {
folder = await this.createFolder(folderName, tag.id, userId);
console.log(`Created new folder: ${folderName}`);
} else {
console.log(`Using existing folder: ${folderName}`);
}

// Create questions
const createdQuestions = await Promise.all(
Expand All @@ -65,7 +116,9 @@ class QuestionSet {
return {
folder,
tag,
questions: createdQuestions
questions: createdQuestions,
isNewFolder,
isNewTag
};
} catch (err) {
await client.query('ROLLBACK');
Expand All @@ -74,6 +127,81 @@ class QuestionSet {
client.release();
}
}

static async getAllQuestionsByUserId(userId) {
console.log('DEBUG: Querying for userId:', userId);
const query = `
SELECT
q.id as question_id,
q.description,
q.options,
q.answer,
q.user_answer,
q.note,
f.id as folder_id,
f.title as folder_title,
t.id as tag_id,
t.name as tag_name
FROM questions q
JOIN folders f ON q.folder_id = f.id
JOIN tags t ON f.tag_id = t.id
WHERE f.user_id = $1
ORDER BY f.id ASC, q.id ASC
`;
const result = await db.query(query, [userId]);
console.log('DEBUG: Raw query result rows count:', result.rows.length);
console.log('DEBUG: Raw query result:', result.rows);

// Group questions by folder
const foldersMap = new Map();

result.rows.forEach(row => {
const folderId = row.folder_id;

if (!foldersMap.has(folderId)) {
foldersMap.set(folderId, {
folder_id: row.folder_id,
folder_title: row.folder_title,
tag_id: row.tag_id,
tag_name: row.tag_name,
questions: []
});
}

foldersMap.get(folderId).questions.push({
question_id: row.question_id,
description: row.description,
options: row.options,
answer: row.answer,
user_answer: row.user_answer,
note: row.note
});
});

// Convert map to array and return
const finalResult = Array.from(foldersMap.values());
console.log('DEBUG: Final structured result:', finalResult);
return finalResult;
}

static async getAllTagsByUserId(userId) {
console.log('DEBUG: Querying tags for userId:', userId);
const query = `
SELECT
id as tag_id,
name as tag_name,
created_at,
updated_at
FROM tags
WHERE user_id = $1
ORDER BY created_at ASC
`;
const result = await db.query(query, [userId]);
console.log('DEBUG: Raw tags query result rows count:', result.rows.length);
console.log('DEBUG: Raw tags query result:', result.rows);

return result.rows;
}
}

module.exports = QuestionSet;
Loading