const { Context, Vocab } = require('../models'); /** * Context Controller - Workflow-based status management * Status flow: 0 (Draft) -> 1 (Enriched) -> 2 (Prompt Ready) -> 3 (Generating) -> 4 (Image Ready) -> 5 (Approved) */ class ContextController { /** * Create new context - Status 0 (Draft) * Required fields: title, desc, grade */ async createContext(req, res, next) { try { const { title, desc, grade, type } = req.body; // Validate required fields if (!title || !desc || !grade) { return res.status(400).json({ success: false, message: 'Title, desc, and grade are required' }); } // Validate grade format (gradeX100 + unitX10 + lesson) const gradeNum = parseInt(grade); if (isNaN(gradeNum) || gradeNum < 100) { return res.status(400).json({ success: false, message: 'Grade must be in format: gradeX100 + unitX10 + lesson (e.g., 123 for Grade 1 Unit 2 Lesson 3)' }); } const context = await Context.create({ title, desc, grade: gradeNum, type: type || 'general', status: 0, // Draft context: '', knowledge: '' }); res.status(201).json({ success: true, message: 'Context created successfully', data: context }); } catch (error) { next(error); } } /** * Get contexts by status */ async getContextsByStatus(req, res, next) { try { const { status } = req.params; const { page = 1, limit = 50, type, grade } = req.query; const offset = (page - 1) * limit; const where = { status: parseInt(status) }; if (type) where.type = type; if (grade) where.grade = parseInt(grade); 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); } } /** * Update knowledge - Status 0 -> 1 (Enriched) */ async enrichContext(req, res, next) { try { const { id } = req.params; const { knowledge } = req.body; if (!knowledge) { return res.status(400).json({ success: false, message: 'Knowledge is required' }); } const context = await Context.findByPk(id); if (!context) { return res.status(404).json({ success: false, message: 'Context not found' }); } if (context.status !== 0) { return res.status(400).json({ success: false, message: 'Context must be in Draft status (0) to enrich' }); } await context.update({ knowledge, status: 1 }); res.json({ success: true, message: 'Context enriched successfully', data: context }); } catch (error) { next(error); } } /** * Update context and img_prompt - Status 1 -> 2 (Prompt Ready) */ async preparePrompt(req, res, next) { try { const { id } = req.params; const { context, img_prompt } = req.body; if (!context || !img_prompt) { return res.status(400).json({ success: false, message: 'Context and img_prompt are required' }); } const contextRecord = await Context.findByPk(id); if (!contextRecord) { return res.status(404).json({ success: false, message: 'Context not found' }); } if (contextRecord.status !== 1) { return res.status(400).json({ success: false, message: 'Context must be in Enriched status (1) to prepare prompt' }); } await contextRecord.update({ context, img_prompt, status: 2 }); res.json({ success: true, message: 'Prompt prepared successfully', data: contextRecord }); } catch (error) { next(error); } } /** * Update status to 3 (Generating) or back to 1 (Enriched) */ async updateStatusFromPromptReady(req, res, next) { try { const { id } = req.params; const { status } = req.body; const context = await Context.findByPk(id); if (!context) { return res.status(404).json({ success: false, message: 'Context not found' }); } await context.update({ status: parseInt(status) }); res.json({ success: true, message: `Status updated to ${status}`, data: context }); } catch (error) { next(error); } } /** * Bulk update all status 2 to status 3 (Prompt Ready -> Generating) */ async bulkUpdateStatus2To3(req, res, next) { try { const [affectedCount] = await Context.update( { status: 3 }, { where: { status: 2 } } ); res.json({ success: true, message: `Updated ${affectedCount} context(s) from status 2 to status 3`, data: { affectedCount } }); } catch (error) { next(error); } } /** * Add images - Status 3 -> 4 (Image Ready) */ async addImages(req, res, next) { try { const { id } = req.params; const { image } = req.body; if (!image || typeof image !== 'string' || image.trim().length === 0) { return res.status(400).json({ success: false, message: 'Image must be a non-empty string (URL)' }); } const context = await Context.findByPk(id); if (!context) { return res.status(404).json({ success: false, message: 'Context not found' }); } if (context.status !== 3) { return res.status(400).json({ success: false, message: 'Context must be in Generating status (3) to add images' }); } await context.update({ image, status: 4 }); res.json({ success: true, message: 'Images added successfully', data: context }); } catch (error) { next(error); } } /** * Approve context - Status 4 -> 5 (Approved) */ async approveContext(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' }); } if (context.status !== 4) { return res.status(400).json({ success: false, message: 'Context must be in Image Ready status (4) to approve' }); } // add image to Vocab Image const currentVocab = await Vocab.findOne({ where: { vocab_id: context.reference_id } }); console.log('Current Vocab:', currentVocab); if (currentVocab) { if (context.type_image === 'small') { const updatedImagesSmall = currentVocab.image_small || []; updatedImagesSmall.push(context.image); await currentVocab.update({ image_small: updatedImagesSmall }); } else if (context.type_image === 'square') { const updatedImagesSquare = currentVocab.image_square || []; updatedImagesSquare.push(context.image); await currentVocab.update({ image_square: updatedImagesSquare }); } else if (context.type_image === 'normal') { const updatedImagesNormal = currentVocab.image_normal || []; updatedImagesNormal.push(context.image); await currentVocab.update({ image_normal: updatedImagesNormal }); } await currentVocab.save(); } await context.update({ status: 5 }); res.json({ success: true, message: 'Context approved successfully', data: context }); } catch (error) { next(error); } } /** * Get context by UUID (for viewing details) */ 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); } } /** * Get all contexts with pagination and filters */ async getAllContexts(req, res, next) { try { const { page = 1, limit = 50, type, grade, status } = req.query; const offset = (page - 1) * limit; const where = {}; if (type) where.type = type; if (grade) where.grade = parseInt(grade); if (status !== undefined) where.status = parseInt(status); 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); } } /** * Update context (general update - use with caution) */ 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();