From 97dbbd4d124adcbc1c481f774eef86f6aaf5d064 Mon Sep 17 00:00:00 2001 From: silverpro89 Date: Mon, 2 Feb 2026 10:05:46 +0700 Subject: [PATCH] Add Context APIs and refactor vocab models Introduce Context and ContextGuide features: add Sequelize models (models/Context.js, models/ContextGuide.js), controllers (controllers/contextController.js, controllers/contextGuideController.js) and authenticated route handlers (routes/contextRoutes.js, routes/contextGuideRoutes.js). Wire the new routes into app.js and export the models from models/index.js. Refactor vocabulary: remove VocabForm, VocabMapping and VocabRelation models and relationships, update models/Vocab.js schema and indexes, and add migrate-vocab.js to drop/recreate the vocab table for the new schema. Also add a lesson editor UI (public/lesson-editor.html) and a small cleanup in models/Lesson.js. --- app.js | 6 + controllers/contextController.js | 139 ++++++++ controllers/contextGuideController.js | 139 ++++++++ migrate-vocab.js | 36 ++ models/Context.js | 92 +++++ models/ContextGuide.js | 51 +++ models/Lesson.js | 1 - models/Vocab.js | 47 ++- models/VocabForm.js | 78 ----- models/VocabMapping.js | 72 ---- models/VocabRelation.js | 65 ---- models/index.js | 27 +- public/lesson-editor.html | 463 ++++++++++++++++++++++++++ routes/contextGuideRoutes.js | 25 ++ routes/contextRoutes.js | 25 ++ 15 files changed, 1018 insertions(+), 248 deletions(-) create mode 100644 controllers/contextController.js create mode 100644 controllers/contextGuideController.js create mode 100644 migrate-vocab.js create mode 100644 models/Context.js create mode 100644 models/ContextGuide.js delete mode 100644 models/VocabForm.js delete mode 100644 models/VocabMapping.js delete mode 100644 models/VocabRelation.js create mode 100644 public/lesson-editor.html create mode 100644 routes/contextGuideRoutes.js create mode 100644 routes/contextRoutes.js diff --git a/app.js b/app.js index f4d2593..7f7d5c8 100644 --- a/app.js +++ b/app.js @@ -40,6 +40,8 @@ const grammarRoutes = require('./routes/grammarRoutes'); const storyRoutes = require('./routes/storyRoutes'); const learningContentRoutes = require('./routes/learningContentRoutes'); const uploadRoutes = require('./routes/uploadRoutes'); +const contextRoutes = require('./routes/contextRoutes'); +const contextGuideRoutes = require('./routes/contextGuideRoutes'); /** * Initialize Express Application @@ -165,6 +167,8 @@ app.get('/api', (req, res) => { games: '/api/games', gameTypes: '/api/game-types', vocab: '/api/vocab', + contexts: '/api/contexts', + contextGuides: '/api/context-guides', upload: '/api/upload', }, documentation: '/api-docs', @@ -224,6 +228,8 @@ app.use('/api/grammar', grammarRoutes); app.use('/api/stories', storyRoutes); app.use('/api/learning-content', learningContentRoutes); app.use('/api/upload', uploadRoutes); +app.use('/api/contexts', contextRoutes); +app.use('/api/context-guides', contextGuideRoutes); /** * Queue Status Endpoint diff --git a/controllers/contextController.js b/controllers/contextController.js new file mode 100644 index 0000000..f6bb63f --- /dev/null +++ b/controllers/contextController.js @@ -0,0 +1,139 @@ +const { Context } = require('../models'); + +/** + * Context Controller + */ +class ContextController { + /** + * Get all contexts with pagination and filters + */ + async getAllContexts(req, res, next) { + try { + const { page = 1, limit = 50, type, title } = req.query; + const offset = (page - 1) * limit; + + const where = {}; + if (type) where.type = type; + if (title) where.title = title; + + const { count, rows } = await Context.findAndCountAll({ + where, + limit: parseInt(limit), + offset: parseInt(offset), + order: [['created_at', 'DESC']] + }); + + res.json({ + success: true, + data: { + contexts: rows, + pagination: { + total: count, + page: parseInt(page), + limit: parseInt(limit), + totalPages: Math.ceil(count / limit) + } + } + }); + } catch (error) { + next(error); + } + } + + /** + * Get context by UUID + */ + async getContextById(req, res, next) { + try { + const { id } = req.params; + const context = await Context.findByPk(id); + + if (!context) { + return res.status(404).json({ + success: false, + message: 'Context not found' + }); + } + + res.json({ + success: true, + data: context + }); + } catch (error) { + next(error); + } + } + + /** + * Create new context + */ + async createContext(req, res, next) { + try { + const context = await Context.create(req.body); + + res.status(201).json({ + success: true, + message: 'Context created successfully', + data: context + }); + } catch (error) { + next(error); + } + } + + /** + * Update context + */ + async updateContext(req, res, next) { + try { + const { id } = req.params; + const updates = req.body; + + const context = await Context.findByPk(id); + if (!context) { + return res.status(404).json({ + success: false, + message: 'Context not found' + }); + } + + await context.update(updates); + + res.json({ + success: true, + message: 'Context updated successfully', + data: context + }); + } catch (error) { + next(error); + } + } + + /** + * Delete context + */ + async deleteContext(req, res, next) { + try { + const { id } = req.params; + const context = await Context.findByPk(id); + + if (!context) { + return res.status(404).json({ + success: false, + message: 'Context not found' + }); + } + + await context.destroy(); + + res.json({ + success: true, + message: 'Context deleted successfully' + }); + } catch (error) { + next(error); + } + } +} + +module.exports = new ContextController(); diff --git a/controllers/contextGuideController.js b/controllers/contextGuideController.js new file mode 100644 index 0000000..d32adde --- /dev/null +++ b/controllers/contextGuideController.js @@ -0,0 +1,139 @@ +const { ContextGuide } = require('../models'); + +/** + * ContextGuide Controller + */ +class ContextGuideController { + /** + * Get all context guides with pagination and filters + */ + async getAllContextGuides(req, res, next) { + try { + const { page = 1, limit = 50, name, title } = req.query; + const offset = (page - 1) * limit; + + const where = {}; + if (name) where.name = name; + if (title) where.title = title; + + const { count, rows } = await ContextGuide.findAndCountAll({ + where, + limit: parseInt(limit), + offset: parseInt(offset), + order: [['created_at', 'DESC']] + }); + + res.json({ + success: true, + data: { + guides: rows, + pagination: { + total: count, + page: parseInt(page), + limit: parseInt(limit), + totalPages: Math.ceil(count / limit) + } + } + }); + } catch (error) { + next(error); + } + } + + /** + * Get context guide by UUID + */ + async getContextGuideById(req, res, next) { + try { + const { id } = req.params; + const guide = await ContextGuide.findByPk(id); + + if (!guide) { + return res.status(404).json({ + success: false, + message: 'Context guide not found' + }); + } + + res.json({ + success: true, + data: guide + }); + } catch (error) { + next(error); + } + } + + /** + * Create new context guide + */ + async createContextGuide(req, res, next) { + try { + const guide = await ContextGuide.create(req.body); + + res.status(201).json({ + success: true, + message: 'Context guide created successfully', + data: guide + }); + } catch (error) { + next(error); + } + } + + /** + * Update context guide + */ + async updateContextGuide(req, res, next) { + try { + const { id } = req.params; + const updates = req.body; + + const guide = await ContextGuide.findByPk(id); + if (!guide) { + return res.status(404).json({ + success: false, + message: 'Context guide not found' + }); + } + + await guide.update(updates); + + res.json({ + success: true, + message: 'Context guide updated successfully', + data: guide + }); + } catch (error) { + next(error); + } + } + + /** + * Delete context guide + */ + async deleteContextGuide(req, res, next) { + try { + const { id } = req.params; + const guide = await ContextGuide.findByPk(id); + + if (!guide) { + return res.status(404).json({ + success: false, + message: 'Context guide not found' + }); + } + + await guide.destroy(); + + res.json({ + success: true, + message: 'Context guide deleted successfully' + }); + } catch (error) { + next(error); + } + } +} + +module.exports = new ContextGuideController(); diff --git a/migrate-vocab.js b/migrate-vocab.js new file mode 100644 index 0000000..4a34caf --- /dev/null +++ b/migrate-vocab.js @@ -0,0 +1,36 @@ +/** + * Drop and recreate Vocab table with new schema + */ +const { sequelize } = require('./config/database'); +const { Vocab } = require('./models'); + +async function migrateVocabTable() { + try { + console.log('🔄 Starting vocab table migration...'); + + await sequelize.authenticate(); + console.log('✅ Database connection OK'); + + // Drop old vocab-related tables + console.log('🗑️ Dropping old vocab-related tables...'); + await sequelize.query('DROP TABLE IF EXISTS `vocab_relation`'); + await sequelize.query('DROP TABLE IF EXISTS `vocab_form`'); + await sequelize.query('DROP TABLE IF EXISTS `vocab_mapping`'); + await sequelize.query('DROP TABLE IF EXISTS `vocab`'); + console.log('✅ Old tables dropped'); + + // Recreate vocab table with new schema + console.log('📊 Creating new vocab table...'); + await Vocab.sync({ force: true }); + console.log('✅ Vocab table created with new schema'); + + console.log('\n✅ Vocab migration complete!'); + process.exit(0); + } catch (error) { + console.error('❌ Error migrating vocab table:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +migrateVocabTable(); diff --git a/models/Context.js b/models/Context.js new file mode 100644 index 0000000..b56f18a --- /dev/null +++ b/models/Context.js @@ -0,0 +1,92 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const Context = sequelize.define('Context', { + uuid: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + comment: 'Unique identifier for context' + }, + title: { + type: DataTypes.STRING(255), + allowNull: false, + comment: 'Title of the context item' + }, + context: { + type: DataTypes.TEXT, + allowNull: false, + comment: 'Context description' + }, + knowledge: { + type: DataTypes.TEXT, + comment: 'Additional knowledge or information' + }, + type: { + type: DataTypes.STRING(50), + allowNull: false, + comment: 'Context type (e.g., vocabulary, grammar)' + }, + desc: { + type: DataTypes.TEXT, + comment: 'Detailed description or requirement' + }, + prompt: { + type: DataTypes.JSON, + comment: 'Prompt configuration object' + }, + image: { + type: DataTypes.JSON, + comment: 'Array of image URLs' + }, + difficulty: { + type: DataTypes.INTEGER, + defaultValue: 1, + comment: 'Difficulty level (1-10)' + }, + max: { + type: DataTypes.INTEGER, + defaultValue: 0, + comment: 'Maximum number of images or items' + }, + isPrompt: { + type: DataTypes.BOOLEAN, + defaultValue: false, + comment: 'Prompt created (0/1)' + }, + isList: { + type: DataTypes.BOOLEAN, + defaultValue: false, + comment: 'Waiting for more images (0/1)' + }, + isApprove: { + type: DataTypes.BOOLEAN, + defaultValue: false, + comment: 'Teacher approval status (0/1)' + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + updated_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } +}, { + tableName: 'context', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + indexes: [ + { + name: 'idx_context_type', + fields: ['type'] + }, + { + name: 'idx_context_title', + fields: ['title'] + } + ] +}); + +module.exports = Context; diff --git a/models/ContextGuide.js b/models/ContextGuide.js new file mode 100644 index 0000000..5766b00 --- /dev/null +++ b/models/ContextGuide.js @@ -0,0 +1,51 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../config/database'); + +const ContextGuide = sequelize.define('ContextGuide', { + uuid: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + comment: 'Unique identifier for context guide' + }, + title: { + type: DataTypes.STRING(255), + allowNull: false, + comment: 'Guide title' + }, + name: { + type: DataTypes.STRING(255), + allowNull: false, + comment: 'Target field name for AI processing' + }, + data: { + type: DataTypes.JSON, + allowNull: false, + comment: 'Guide data (text or json)' + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + }, + updated_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW + } +}, { + tableName: 'context_guide', + timestamps: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + indexes: [ + { + name: 'idx_context_guide_title', + fields: ['title'] + }, + { + name: 'idx_context_guide_name', + fields: ['name'] + } + ] +}); + +module.exports = ContextGuide; diff --git a/models/Lesson.js b/models/Lesson.js index c728a7c..919424f 100644 --- a/models/Lesson.js +++ b/models/Lesson.js @@ -63,7 +63,6 @@ const Lesson = sequelize.define('lessons', { type: DataTypes.STRING(50), comment: 'Loại content cho URL: video, audio, pdf, external_link, youtube, etc.' }, - duration_minutes: { type: DataTypes.INTEGER, comment: 'Thời lượng (phút)' diff --git a/models/Vocab.js b/models/Vocab.js index 5555b76..a6e1c42 100644 --- a/models/Vocab.js +++ b/models/Vocab.js @@ -8,21 +8,32 @@ const Vocab = sequelize.define('Vocab', { primaryKey: true, comment: 'Unique identifier for vocabulary entry' }, - vocab_code: { - type: DataTypes.STRING(50), - unique: true, - allowNull: false, - comment: 'Unique code for vocabulary (e.g., vocab-001-eat)' + // Từ thực tế (wash, washes, washing, ate, eaten...) + text: { + type: DataTypes.STRING(100), + allowNull: false, + index: true }, - base_word: { - type: DataTypes.STRING(100), - allowNull: false, - comment: 'Base form of the word' + // Từ gốc để nhóm lại (wash, eat...) + base_word: { + type: DataTypes.STRING(100), + allowNull: false, + index: true }, + // Đã xuất hiện trong khối nào, bài học nào, lesson nào + // Ví dụ 111 là grade 1, unit 1, lesson 1 + location: { + type: DataTypes.INTEGER, + comment: 'Location or source of the vocabulary' + }, + // Loại biến thể (V1, V2, V3, V_ing, Noun_Form...) + form_key: { + type: DataTypes.STRING(20), + defaultValue: 'base' + }, + // Nội dung dùng chung (có thể lưu JSON để dễ quản lý hoặc dùng chung cho cả group) translation: { - type: DataTypes.STRING(200), - allowNull: false, - comment: 'Vietnamese translation' + type: DataTypes.STRING(200) }, difficulty_score: { type: DataTypes.INTEGER, @@ -33,6 +44,10 @@ const Vocab = sequelize.define('Vocab', { type: DataTypes.STRING(100), comment: 'Category of the word (e.g., Action Verbs, Nouns)' }, + topic: { + type: DataTypes.STRING(100), + comment: 'Topic of the word (e.g., Food, Travel, Education)' + }, images: { type: DataTypes.JSON, comment: 'Array of image URLs' @@ -73,8 +88,8 @@ const Vocab = sequelize.define('Vocab', { updatedAt: 'updated_at', indexes: [ { - name: 'idx_vocab_code', - fields: ['vocab_code'] + name: 'idx_vocab_text', + fields: ['text'] }, { name: 'idx_base_word', @@ -83,6 +98,10 @@ const Vocab = sequelize.define('Vocab', { { name: 'idx_category', fields: ['category'] + }, + { + name: 'idx_location', + fields: ['location'] } ] }); diff --git a/models/VocabForm.js b/models/VocabForm.js deleted file mode 100644 index be53f8a..0000000 --- a/models/VocabForm.js +++ /dev/null @@ -1,78 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const VocabForm = sequelize.define('VocabForm', { - form_id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - comment: 'Unique identifier for word form' - }, - vocab_id: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'vocab', - key: 'vocab_id' - }, - onDelete: 'CASCADE', - comment: 'Reference to vocabulary entry' - }, - form_key: { - type: DataTypes.STRING(50), - allowNull: false, - comment: 'Form identifier (v1, v2, v3, v_s_es, v_ing, etc.)' - }, - text: { - type: DataTypes.STRING(100), - allowNull: false, - comment: 'The actual word form (e.g., eat, eats, eating, ate)' - }, - phonetic: { - type: DataTypes.STRING(100), - comment: 'IPA phonetic transcription (e.g., /iːt/)' - }, - audio_url: { - type: DataTypes.STRING(500), - comment: 'URL to audio pronunciation file' - }, - min_grade: { - type: DataTypes.INTEGER, - defaultValue: 1, - comment: 'Minimum grade level to unlock this form' - }, - description: { - type: DataTypes.TEXT, - comment: 'Description or usage note for this form' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - } - }, { - tableName: 'vocab_form', - timestamps: true, - createdAt: 'created_at', - updatedAt: 'updated_at', - indexes: [ - { - name: 'idx_vocab_form_vocab', - fields: ['vocab_id'] - }, - { - name: 'idx_vocab_form_key', - fields: ['form_key'] - }, - { - name: 'idx_vocab_form_unique', - unique: true, - fields: ['vocab_id', 'form_key'] - } - ] - }); - -module.exports = VocabForm; diff --git a/models/VocabMapping.js b/models/VocabMapping.js deleted file mode 100644 index ba505b7..0000000 --- a/models/VocabMapping.js +++ /dev/null @@ -1,72 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const VocabMapping = sequelize.define('VocabMapping', { - mapping_id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - comment: 'Unique identifier for curriculum mapping' - }, - vocab_id: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'vocab', - key: 'vocab_id' - }, - onDelete: 'CASCADE', - comment: 'Reference to vocabulary entry' - }, - book_id: { - type: DataTypes.STRING(100), - allowNull: false, - comment: 'Book/curriculum identifier (e.g., global-success-1)' - }, - grade: { - type: DataTypes.INTEGER, - allowNull: false, - comment: 'Grade level' - }, - unit: { - type: DataTypes.INTEGER, - comment: 'Unit number in the book' - }, - lesson: { - type: DataTypes.INTEGER, - comment: 'Lesson number in the unit' - }, - form_key: { - type: DataTypes.STRING(50), - comment: 'Which form to use (v1, v_s_es, v_ing, v2, v3, etc.)' - }, - context_note: { - type: DataTypes.TEXT, - comment: 'Additional context for this mapping' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - } - }, { - tableName: 'vocab_mapping', - timestamps: true, - createdAt: 'created_at', - updatedAt: false, - indexes: [ - { - name: 'idx_vocab_mapping_vocab', - fields: ['vocab_id'] - }, - { - name: 'idx_vocab_mapping_book_grade', - fields: ['book_id', 'grade'] - }, - { - name: 'idx_vocab_mapping_unit_lesson', - fields: ['unit', 'lesson'] - } - ] - }); - -module.exports = VocabMapping; diff --git a/models/VocabRelation.js b/models/VocabRelation.js deleted file mode 100644 index c75c605..0000000 --- a/models/VocabRelation.js +++ /dev/null @@ -1,65 +0,0 @@ -const { DataTypes } = require('sequelize'); -const { sequelize } = require('../config/database'); - -const VocabRelation = sequelize.define('VocabRelation', { - relation_id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - comment: 'Unique identifier for vocabulary relation' - }, - vocab_id: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'vocab', - key: 'vocab_id' - }, - onDelete: 'CASCADE', - comment: 'Reference to vocabulary entry' - }, - relation_type: { - type: DataTypes.ENUM('synonym', 'antonym', 'related'), - allowNull: false, - comment: 'Type of relation (synonym, antonym, related)' - }, - related_word: { - type: DataTypes.STRING(100), - allowNull: false, - comment: 'The related word (e.g., consume, dine, fast)' - }, - related_vocab_id: { - type: DataTypes.UUID, - references: { - model: 'vocab', - key: 'vocab_id' - }, - onDelete: 'SET NULL', - comment: 'Reference to related vocab entry if it exists in system' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - } - }, { - tableName: 'vocab_relation', - timestamps: true, - createdAt: 'created_at', - updatedAt: false, - indexes: [ - { - name: 'idx_vocab_relation_vocab', - fields: ['vocab_id'] - }, - { - name: 'idx_vocab_relation_type', - fields: ['relation_type'] - }, - { - name: 'idx_vocab_relation_related', - fields: ['related_vocab_id'] - } - ] - }); - -module.exports = VocabRelation; diff --git a/models/index.js b/models/index.js index 8408ba2..fd719cc 100644 --- a/models/index.js +++ b/models/index.js @@ -35,9 +35,6 @@ const LessonLeaderboard = require('./LessonLeaderboard'); // Group 3.2: Vocabulary System (NEW) const Vocab = require('./Vocab'); -const VocabMapping = require('./VocabMapping'); -const VocabForm = require('./VocabForm'); -const VocabRelation = require('./VocabRelation'); // Group 3.3: Grammar System (NEW) const Grammar = require('./Grammar'); @@ -47,6 +44,10 @@ const GrammarMediaStory = require('./GrammarMediaStory'); // Group 3.4: Story System (NEW) const Story = require('./Story'); +// Group 3.5: Context (NEW) +const Context = require('./Context'); +const ContextGuide = require('./ContextGuide'); + // Group 4: Attendance const AttendanceLog = require('./AttendanceLog'); const AttendanceDaily = require('./AttendanceDaily'); @@ -164,18 +165,7 @@ const setupRelationships = () => { Lesson.hasMany(LessonLeaderboard, { foreignKey: 'lesson_id', as: 'leaderboard' }); // Vocabulary relationships (NEW) - // Vocab -> VocabMapping (1:N) - Vocab.hasMany(VocabMapping, { foreignKey: 'vocab_id', as: 'mappings' }); - VocabMapping.belongsTo(Vocab, { foreignKey: 'vocab_id', as: 'vocab' }); - - // Vocab -> VocabForm (1:N) - Vocab.hasMany(VocabForm, { foreignKey: 'vocab_id', as: 'forms' }); - VocabForm.belongsTo(Vocab, { foreignKey: 'vocab_id', as: 'vocab' }); - - // Vocab -> VocabRelation (1:N) - Vocab.hasMany(VocabRelation, { foreignKey: 'vocab_id', as: 'relations' }); - VocabRelation.belongsTo(Vocab, { foreignKey: 'vocab_id', as: 'vocab' }); - VocabRelation.belongsTo(Vocab, { foreignKey: 'related_vocab_id', as: 'relatedVocab' }); + // No additional relationships needed // Grammar relationships (NEW) // Grammar -> GrammarMapping (1:N) @@ -299,9 +289,6 @@ module.exports = { // Group 3.2: Vocabulary System (NEW) Vocab, - VocabMapping, - VocabForm, - VocabRelation, // Group 3.3: Grammar System (NEW) Grammar, @@ -310,6 +297,10 @@ module.exports = { // Group 3.4: Story System (NEW) Story, + + // Group 3.5: Context (NEW) + Context, + ContextGuide, // Group 4: Attendance AttendanceLog, diff --git a/public/lesson-editor.html b/public/lesson-editor.html new file mode 100644 index 0000000..27b81b4 --- /dev/null +++ b/public/lesson-editor.html @@ -0,0 +1,463 @@ + + + + + + Lesson Editor - SENA + + + +
+
+

Lesson Editor

+

Cập nhật nội dung Lesson qua API /api/lessons/:id

+
+
+
+
+ + +
Mặc định theo domain hiện tại
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ +
Thông tin cơ bản
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
Content JSON
+
+ + +
Nếu rỗng thì không gửi content_json
+
+ +
Content URL
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
Trạng thái
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+
+ + + + diff --git a/routes/contextGuideRoutes.js b/routes/contextGuideRoutes.js new file mode 100644 index 0000000..66f4ebe --- /dev/null +++ b/routes/contextGuideRoutes.js @@ -0,0 +1,25 @@ +const express = require('express'); +const router = express.Router(); +const contextGuideController = require('../controllers/contextGuideController'); +const { authenticateToken } = require('../middleware/auth'); + +/** + * ContextGuide Routes + */ + +// Get all context guides +router.get('/', authenticateToken, contextGuideController.getAllContextGuides); + +// Get context guide by UUID +router.get('/:id', authenticateToken, contextGuideController.getContextGuideById); + +// Create context guide +router.post('/', authenticateToken, contextGuideController.createContextGuide); + +// Update context guide +router.put('/:id', authenticateToken, contextGuideController.updateContextGuide); + +// Delete context guide +router.delete('/:id', authenticateToken, contextGuideController.deleteContextGuide); + +module.exports = router; diff --git a/routes/contextRoutes.js b/routes/contextRoutes.js new file mode 100644 index 0000000..938e94f --- /dev/null +++ b/routes/contextRoutes.js @@ -0,0 +1,25 @@ +const express = require('express'); +const router = express.Router(); +const contextController = require('../controllers/contextController'); +const { authenticateToken } = require('../middleware/auth'); + +/** + * Context Routes + */ + +// Get all contexts +router.get('/', authenticateToken, contextController.getAllContexts); + +// Get context by UUID +router.get('/:id', authenticateToken, contextController.getContextById); + +// Create context +router.post('/', authenticateToken, contextController.createContext); + +// Update context +router.put('/:id', authenticateToken, contextController.updateContext); + +// Delete context +router.delete('/:id', authenticateToken, contextController.deleteContext); + +module.exports = router;