update sentences API
All checks were successful
Deploy to Production / deploy (push) Successful in 22s
All checks were successful
Deploy to Production / deploy (push) Successful in 22s
This commit is contained in:
796
controllers/sentencesController.js
Normal file
796
controllers/sentencesController.js
Normal file
@@ -0,0 +1,796 @@
|
||||
const { Sentences } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* CREATE SENTENCE
|
||||
* ============================================
|
||||
* Tạo mới một sentence entry
|
||||
*
|
||||
* @route POST /api/sentences
|
||||
* @access Private (requires authentication)
|
||||
*
|
||||
* INPUT:
|
||||
* {
|
||||
* text: String (required) - nội dung câu
|
||||
* ipa: String - phiên âm IPA
|
||||
* vi: String - nghĩa tiếng Việt
|
||||
* category: String - category của câu (e.g., Action Verbs, Nouns)
|
||||
* topic: String - chủ đề (e.g., Food, Travel, Education)
|
||||
* image_small: JSON Array - mảng URLs của hình ảnh nhỏ
|
||||
* image_square: JSON Array - mảng URLs của hình ảnh vuông
|
||||
* image_normal: JSON Array - mảng URLs của hình ảnh bình thường
|
||||
* audio: JSON Array - mảng URLs của audio files
|
||||
* tags: JSON Array - các tags để phân loại
|
||||
* usage_note: String - lưu ý về ngữ cảnh sử dụng
|
||||
* etc: String - các thông tin khác
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Sentence object đã tạo
|
||||
* }
|
||||
*/
|
||||
exports.createSentence = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
text,
|
||||
ipa,
|
||||
vi,
|
||||
category,
|
||||
topic,
|
||||
image_small,
|
||||
image_square,
|
||||
image_normal,
|
||||
audio,
|
||||
tags,
|
||||
usage_note,
|
||||
etc
|
||||
} = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!text) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'text is a required field'
|
||||
});
|
||||
}
|
||||
|
||||
const sentence = await Sentences.create({
|
||||
text,
|
||||
ipa: ipa || null,
|
||||
vi: vi || '',
|
||||
category: category || null,
|
||||
topic: topic || null,
|
||||
image_small: image_small || [],
|
||||
image_square: image_square || [],
|
||||
image_normal: image_normal || [],
|
||||
audio: audio || null,
|
||||
tags: tags || [],
|
||||
usage_note: usage_note || '',
|
||||
etc: etc || '',
|
||||
is_active: true
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Sentence created successfully',
|
||||
data: sentence
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating sentence:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error creating sentence',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET ALL SENTENCES
|
||||
* ============================================
|
||||
* Lấy danh sách sentences với phân trang và filter
|
||||
*
|
||||
* @route GET /api/sentences
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (Query Parameters):
|
||||
* {
|
||||
* page: Number - trang hiện tại (mặc định: 1)
|
||||
* limit: Number - số items mỗi trang (mặc định: 20)
|
||||
* category: String - lọc theo category
|
||||
* topic: String - lọc theo topic
|
||||
* text: String - lọc theo text chính xác
|
||||
* search: String - tìm kiếm trong text và vi
|
||||
* is_active: Boolean - lọc theo trạng thái active (mặc định: true)
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of Sentence objects,
|
||||
* pagination: { total, page, limit, totalPages }
|
||||
* }
|
||||
*/
|
||||
exports.getAllSentences = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
category,
|
||||
topic,
|
||||
text,
|
||||
search,
|
||||
is_active = true
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const where = {};
|
||||
|
||||
if (is_active !== undefined) {
|
||||
where.is_active = is_active === 'true' || is_active === true;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
where.category = category;
|
||||
}
|
||||
|
||||
if (topic) {
|
||||
where.topic = topic;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
where.text = text;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ text: { [Op.like]: `%${search}%` } },
|
||||
{ vi: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
const { count, rows } = await Sentences.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentences retrieved successfully',
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting sentences:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving sentences',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET SENTENCE BY ID
|
||||
* ============================================
|
||||
* Lấy chi tiết một sentence theo id
|
||||
*
|
||||
* @route GET /api/sentences/:id
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (URL Parameter):
|
||||
* {
|
||||
* id: UUID - id của sentence cần lấy
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Sentence object với đầy đủ thông tin
|
||||
* }
|
||||
*/
|
||||
exports.getSentenceById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const sentence = await Sentences.findOne({
|
||||
where: {
|
||||
id,
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!sentence) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Sentence not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentence retrieved successfully',
|
||||
data: sentence
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting sentence:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving sentence',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET SENTENCES WITHOUT IPA
|
||||
* ============================================
|
||||
* Lấy tất cả các sentences chưa có IPA
|
||||
*
|
||||
* @route GET /api/sentences/missing/ipa
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (Query Parameters):
|
||||
* {
|
||||
* page: Number (mặc định: 1)
|
||||
* limit: Number (mặc định: 50)
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of Sentence objects,
|
||||
* pagination: { total, page, limit, totalPages }
|
||||
* }
|
||||
*/
|
||||
exports.getSentencesWithoutIpa = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 50 } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows } = await Sentences.findAndCountAll({
|
||||
where: {
|
||||
is_active: true,
|
||||
[Op.or]: [
|
||||
{ ipa: null },
|
||||
{ ipa: '' }
|
||||
]
|
||||
},
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [['text', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentences without IPA retrieved successfully',
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting sentences without IPA:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving sentences without IPA',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET SENTENCES WITHOUT IMAGES
|
||||
* ============================================
|
||||
* Lấy tất cả các sentences chưa đủ hình ảnh
|
||||
*
|
||||
* @route GET /api/sentences/missing/images
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (Query Parameters):
|
||||
* {
|
||||
* page: Number (mặc định: 1)
|
||||
* limit: Number (mặc định: 50)
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of Sentence objects,
|
||||
* pagination: { total, page, limit, totalPages }
|
||||
* }
|
||||
*/
|
||||
exports.getSentencesWithoutImages = async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 50 } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows } = await Sentences.findAndCountAll({
|
||||
where: {
|
||||
is_active: true,
|
||||
[Op.or]: [
|
||||
{ image_small: null },
|
||||
{ image_square: null },
|
||||
{ image_normal: null }
|
||||
]
|
||||
},
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [['text', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentences without images retrieved successfully',
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting sentences without images:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving sentences without images',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* UPDATE SENTENCE
|
||||
* ============================================
|
||||
* Cập nhật thông tin sentence
|
||||
*
|
||||
* @route PUT /api/sentences/:id
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (URL Parameter + Body):
|
||||
* {
|
||||
* id: UUID - id cần update
|
||||
* Body: Object - các trường cần update
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Updated Sentence object
|
||||
* }
|
||||
*/
|
||||
exports.updateSentence = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
const sentence = await Sentences.findOne({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (!sentence) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Sentence not found'
|
||||
});
|
||||
}
|
||||
|
||||
await sentence.update(updateData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentence updated successfully',
|
||||
data: sentence
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating sentence:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error updating sentence',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* DELETE SENTENCE (SOFT DELETE)
|
||||
* ============================================
|
||||
* Xóa mềm sentence (set is_active = false)
|
||||
*
|
||||
* @route DELETE /api/sentences/:id
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (URL Parameter):
|
||||
* {
|
||||
* id: UUID - id cần xóa
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String
|
||||
* }
|
||||
*/
|
||||
exports.deleteSentence = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const sentence = await Sentences.findOne({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (!sentence) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Sentence not found'
|
||||
});
|
||||
}
|
||||
|
||||
await sentence.update({ is_active: false });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Sentence deleted successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting sentence:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting sentence',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* BULK CREATE SENTENCES
|
||||
* ============================================
|
||||
* Tạo nhiều sentences cùng lúc
|
||||
*
|
||||
* @route POST /api/sentences/bulk
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (Body):
|
||||
* {
|
||||
* sentences: Array of Sentence objects - mỗi object phải có text
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of created Sentence objects,
|
||||
* count: Number - số lượng đã tạo
|
||||
* }
|
||||
*/
|
||||
exports.bulkCreateSentences = async (req, res) => {
|
||||
try {
|
||||
const { sentences } = req.body;
|
||||
|
||||
if (!Array.isArray(sentences) || sentences.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'sentences must be a non-empty array'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate each sentence has required fields
|
||||
for (let i = 0; i < sentences.length; i++) {
|
||||
if (!sentences[i].text) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `Sentence at index ${i} is missing required field (text)`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const createdSentences = await Sentences.bulkCreate(sentences);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: `${createdSentences.length} sentences created successfully`,
|
||||
data: createdSentences,
|
||||
count: createdSentences.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error bulk creating sentences:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error creating sentences',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET SENTENCE STATISTICS
|
||||
* ============================================
|
||||
* Lấy thống kê về sentences
|
||||
*
|
||||
* @route GET /api/sentences/stats/overview
|
||||
* @access Private
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: {
|
||||
* total: { active, inactive, all },
|
||||
* by_category: Array [{category, count}],
|
||||
* by_topic: Array [{topic, count}]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
exports.getSentenceStats = async (req, res) => {
|
||||
try {
|
||||
const { sequelize } = Sentences;
|
||||
|
||||
const totalActive = await Sentences.count({ where: { is_active: true } });
|
||||
const totalInactive = await Sentences.count({ where: { is_active: false } });
|
||||
|
||||
const byCategory = await Sentences.findAll({
|
||||
where: { is_active: true },
|
||||
attributes: [
|
||||
'category',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['category'],
|
||||
order: [[sequelize.fn('COUNT', sequelize.col('id')), 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const byTopic = await Sentences.findAll({
|
||||
where: { is_active: true },
|
||||
attributes: [
|
||||
'topic',
|
||||
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['topic'],
|
||||
order: [[sequelize.fn('COUNT', sequelize.col('id')), 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Statistics retrieved successfully',
|
||||
data: {
|
||||
total: {
|
||||
active: totalActive,
|
||||
inactive: totalInactive,
|
||||
all: totalActive + totalInactive
|
||||
},
|
||||
by_category: byCategory,
|
||||
by_topic: byTopic
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting sentence stats:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving statistics',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* SEARCH SENTENCES
|
||||
* ============================================
|
||||
* Tìm kiếm sentences nâng cao với nhiều filter
|
||||
*
|
||||
* @route POST /api/sentences/search
|
||||
* @access Private
|
||||
*
|
||||
* INPUT (Body):
|
||||
* {
|
||||
* topic: String (optional)
|
||||
* category: String (optional)
|
||||
* text: String (optional) - partial match
|
||||
* vi: String (optional) - partial match
|
||||
* tags: Array (optional) - lọc theo tags
|
||||
* page: Number (mặc định: 1)
|
||||
* limit: Number (mặc định: 100)
|
||||
* }
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of Sentence objects,
|
||||
* pagination: { total, page, limit, totalPages }
|
||||
* }
|
||||
*/
|
||||
exports.searchSentences = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
topic,
|
||||
category,
|
||||
text,
|
||||
vi,
|
||||
page = 1,
|
||||
limit = 100
|
||||
} = req.body;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const where = { is_active: true };
|
||||
|
||||
if (topic) {
|
||||
where.topic = topic;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
where.category = category;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
where.text = { [Op.like]: `%${text}%` };
|
||||
}
|
||||
|
||||
if (vi) {
|
||||
where.vi = { [Op.like]: `%${vi}%` };
|
||||
}
|
||||
|
||||
const { count, rows } = await Sentences.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [['text', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Search completed successfully',
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error searching sentences:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching sentences',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET ALL CATEGORIES
|
||||
* ============================================
|
||||
* Lấy danh sách tất cả categories có trong database
|
||||
*
|
||||
* @route GET /api/sentences/meta/categories
|
||||
* @access Private
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of String,
|
||||
* count: Number
|
||||
* }
|
||||
*/
|
||||
exports.getAllCategories = async (req, res) => {
|
||||
try {
|
||||
const categories = await Sentences.findAll({
|
||||
where: {
|
||||
is_active: true,
|
||||
category: { [Op.ne]: null }
|
||||
},
|
||||
attributes: ['category'],
|
||||
group: ['category'],
|
||||
order: [['category', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const categoryList = categories.map(c => c.category);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Categories retrieved successfully',
|
||||
data: categoryList,
|
||||
count: categoryList.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting categories:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving categories',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* GET ALL TOPICS
|
||||
* ============================================
|
||||
* Lấy danh sách tất cả topics có trong database
|
||||
*
|
||||
* @route GET /api/sentences/meta/topics
|
||||
* @access Private
|
||||
*
|
||||
* OUTPUT:
|
||||
* {
|
||||
* success: Boolean,
|
||||
* message: String,
|
||||
* data: Array of String,
|
||||
* count: Number
|
||||
* }
|
||||
*/
|
||||
exports.getAllTopics = async (req, res) => {
|
||||
try {
|
||||
const topics = await Sentences.findAll({
|
||||
where: {
|
||||
is_active: true,
|
||||
topic: { [Op.ne]: null }
|
||||
},
|
||||
attributes: ['topic'],
|
||||
group: ['topic'],
|
||||
order: [['topic', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const topicList = topics.map(t => t.topic);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Topics retrieved successfully',
|
||||
data: topicList,
|
||||
count: topicList.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting topics:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error retrieving topics',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user