/** * Submissions Controller * API endpoints for submission tracking */ const SubmissionTracking = require('../models/SubmissionTracking.model'); const BlogPost = require('../models/BlogPost.model'); const publicationTargets = require('../config/publication-targets.config'); /** * POST /api/submissions * Create new submission tracking entry */ async function createSubmission(req, res) { try { const { blogPostId, publicationId, submissionMethod, submissionEmail, submissionUrl, notes } = req.body; // Validate blog post exists const blogPost = await BlogPost.findById(blogPostId); if (!blogPost) { return res.status(404).json({ success: false, error: 'Blog post not found' }); } // Validate publication exists const publication = publicationTargets[publicationId]; if (!publication) { return res.status(404).json({ success: false, error: 'Publication not found' }); } // Create submission tracking entry const submission = await SubmissionTracking.create({ blogPostId, publicationId, publicationName: publication.name, title: blogPost.title, wordCount: blogPost.content ? blogPost.content.split(/\s+/).length : 0, contentType: publication.type, submissionMethod: submissionMethod || publication.submission?.method, submissionEmail: submissionEmail || publication.submission?.email, submissionUrl: submissionUrl || publication.submission?.url, expectedResponseDays: publication.submission?.responseTime?.max || null, createdBy: req.user._id, notes: notes ? [{ content: notes, author: req.user._id }] : [] }); res.status(201).json({ success: true, data: submission }); } catch (error) { console.error('[Submissions] Create submission error:', error); res.status(500).json({ success: false, error: 'Failed to create submission tracking' }); } } /** * GET /api/submissions * Get all submissions with optional filtering */ async function getSubmissions(req, res) { console.log('[SUBMISSIONS DEBUG] getSubmissions called, query:', req.query); try { const { status, publicationId, limit = 50, offset = 0 } = req.query; const query = {}; if (status) query.status = status; if (publicationId) query.publicationId = publicationId; // NOTE: BlogPost and User are native MongoDB classes, not Mongoose models, // so we can't use .populate() for them. Fetch data separately if needed. const submissions = await SubmissionTracking.find(query) .sort({ submittedAt: -1, createdAt: -1 }) .limit(parseInt(limit, 10)) .skip(parseInt(offset, 10)); // Manually fetch blog post titles for submissions that have blogPostId const submissionsWithBlogData = await Promise.all( submissions.map(async submission => { const submissionObj = submission.toObject(); if (submissionObj.blogPostId) { try { const blogPost = await BlogPost.findById(submissionObj.blogPostId); if (blogPost) { submissionObj.blogPost = { title: blogPost.title, slug: blogPost.slug }; } } catch (err) { console.error(`[Submissions] Error fetching blog post ${submissionObj.blogPostId}:`, err.message); } } return submissionObj; }) ); const total = await SubmissionTracking.countDocuments(query); res.json({ success: true, count: submissionsWithBlogData.length, total, data: submissionsWithBlogData }); } catch (error) { console.error('[Submissions] Get submissions error:', error); res.status(500).json({ success: false, error: 'Failed to fetch submissions' }); } } /** * GET /api/submissions/:id * Get specific submission by ID */ async function getSubmissionById(req, res) { try { const { id } = req.params; const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } const submissionObj = submission.toObject(); // Manually fetch blog post data if blogPostId exists if (submissionObj.blogPostId) { try { const blogPost = await BlogPost.findById(submissionObj.blogPostId); if (blogPost) { submissionObj.blogPost = { title: blogPost.title, slug: blogPost.slug, content: blogPost.content }; } } catch (err) { console.error(`[Submissions] Error fetching blog post:`, err.message); } } res.json({ success: true, data: submissionObj }); } catch (error) { console.error('[Submissions] Get submission by ID error:', error); res.status(500).json({ success: false, error: 'Failed to fetch submission' }); } } /** * PUT /api/submissions/:id/status * Update submission status */ async function updateSubmissionStatus(req, res) { try { const { id } = req.params; const { status, feedback, reasonForRejection, publishedUrl, publishedTitle } = req.body; const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } // Update status with automatic timestamp management await submission.updateStatus(status, req.user._id); // Update additional fields if provided if (feedback) submission.feedback = feedback; if (reasonForRejection) submission.reasonForRejection = reasonForRejection; if (publishedUrl) submission.publishedUrl = publishedUrl; if (publishedTitle) submission.publishedTitle = publishedTitle; await submission.save(); res.json({ success: true, data: submission }); } catch (error) { console.error('[Submissions] Update status error:', error); res.status(500).json({ success: false, error: 'Failed to update submission status' }); } } /** * POST /api/submissions/:id/notes * Add note to submission */ async function addSubmissionNote(req, res) { try { const { id } = req.params; const { content } = req.body; if (!content) { return res.status(400).json({ success: false, error: 'Note content is required' }); } const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } await submission.addNote(content, req.user._id); res.json({ success: true, data: submission }); } catch (error) { console.error('[Submissions] Add note error:', error); res.status(500).json({ success: false, error: 'Failed to add note' }); } } /** * GET /api/submissions/statistics * Get submission statistics */ async function getSubmissionStatistics(req, res) { try { const stats = await SubmissionTracking.getStatistics(); res.json({ success: true, data: stats }); } catch (error) { console.error('[Submissions] Get statistics error:', error); res.status(500).json({ success: false, error: 'Failed to fetch statistics' }); } } /** * GET /api/submissions/publication/:publicationId * Get submissions for specific publication with acceptance rate */ async function getSubmissionsByPublication(req, res) { try { const { publicationId } = req.params; const submissions = await SubmissionTracking.getByPublication(publicationId); const acceptanceRate = await SubmissionTracking.getAcceptanceRate(publicationId); const avgResponseTime = await SubmissionTracking.getAverageResponseTime(publicationId); res.json({ success: true, data: { submissions, acceptanceRate: Math.round(acceptanceRate * 10) / 10, avgResponseTimeHours: avgResponseTime } }); } catch (error) { console.error('[Submissions] Get by publication error:', error); res.status(500).json({ success: false, error: 'Failed to fetch submissions for publication' }); } } /** * GET /api/submissions/by-blog-post/:blogPostId * Get submission by blog post ID */ async function getSubmissionByBlogPost(req, res) { try { const { blogPostId } = req.params; const submission = await SubmissionTracking.findOne({ blogPostId }); if (!submission) { return res.status(404).json({ success: false, error: 'No submission found for this blog post' }); } const submissionObj = submission.toObject(); // Manually fetch blog post data try { const blogPost = await BlogPost.findById(blogPostId); if (blogPost) { submissionObj.blogPost = { title: blogPost.title, slug: blogPost.slug, content: blogPost.content }; } } catch (err) { console.error(`[Submissions] Error fetching blog post:`, err.message); } res.json({ success: true, data: submissionObj }); } catch (error) { console.error('[Submissions] Get by blog post error:', error); res.status(500).json({ success: false, error: 'Failed to fetch submission' }); } } /** * PUT /api/submissions/:id * Update submission entry */ async function updateSubmission(req, res) { try { const { id } = req.params; const updateData = req.body; const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } // Update documents if provided if (updateData.documents) { for (const [docType, docData] of Object.entries(updateData.documents)) { if (docData.versions && docData.versions.length > 0) { const version = docData.versions[0]; await submission.setDocumentVersion( docType, version.language, version.content, { translatedBy: version.translatedBy, approved: version.approved } ); } } delete updateData.documents; // Remove from update data since it's handled separately } // Update other fields Object.assign(submission, updateData); submission.lastUpdatedBy = req.user._id; await submission.save(); res.json({ success: true, data: submission }); } catch (error) { console.error('[Submissions] Update submission error:', error); res.status(500).json({ success: false, error: 'Failed to update submission' }); } } /** * GET /api/submissions/:id/export * Export submission package */ async function exportSubmission(req, res) { try { const { id } = req.params; const { format = 'json' } = req.query; const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } const language = req.query.language || 'en'; const packageData = submission.exportPackage(language); // Add blog post data if blogPostId exists if (submission.blogPostId) { try { const blogPost = await BlogPost.findById(submission.blogPostId); if (blogPost) { packageData.blogPost = { title: blogPost.title, content: blogPost.content }; } } catch (err) { console.error(`[Submissions] Error fetching blog post:`, err.message); } } if (format === 'json') { res.json({ success: true, data: packageData }); } else if (format === 'text') { // For now, return JSON with instructions // In future, could zip individual text files res.json({ success: true, data: packageData, note: 'Text export feature coming soon. Please use JSON export for now.' }); } else { res.status(400).json({ success: false, error: 'Invalid format. Use "json" or "text"' }); } } catch (error) { console.error('[Submissions] Export submission error:', error); res.status(500).json({ success: false, error: 'Failed to export submission' }); } } /** * DELETE /api/submissions/:id * Delete submission tracking entry */ async function deleteSubmission(req, res) { try { const { id } = req.params; const submission = await SubmissionTracking.findByIdAndDelete(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } res.json({ success: true, message: 'Submission tracking deleted' }); } catch (error) { console.error('[Submissions] Delete submission error:', error); res.status(500).json({ success: false, error: 'Failed to delete submission' }); } } /** * POST /api/submissions/:id/translate * Translate document using DeepL */ async function translateDocument(req, res) { try { const { id } = req.params; const { docType, fromLang, toLang, text } = req.body; // Validate inputs if (!docType || !fromLang || !toLang || !text) { return res.status(400).json({ success: false, error: 'Missing required fields: docType, fromLang, toLang, text' }); } // Load translation service const translationService = require('../services/Translation.service').getInstance(); // Check if service is available const status = translationService.getStatus(); if (!status.available) { return res.status(503).json({ success: false, error: 'Translation service not available. Please configure DEEPL_API_KEY in .env file.' }); } console.log(`[Translation] ${fromLang} → ${toLang} for ${docType} (${text.length} chars)`); // Perform translation const result = await translationService.translate(text, fromLang, toLang); if (result.error) { return res.status(500).json({ success: false, error: result.errorMessage || 'Translation failed' }); } // Save translation to submission const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ success: false, error: 'Submission not found' }); } // Add/update the translated version await submission.setDocumentVersion( docType, toLang, result.translatedText, { translatedBy: 'deepl', approved: false } ); console.log(`[Translation] Saved ${toLang} version of ${docType} to submission ${id}`); res.json({ success: true, translatedText: result.translatedText, cached: result.cached, detectedLang: result.detectedLang, service: 'DeepL' }); } catch (error) { console.error('[Translation] Error:', error); res.status(500).json({ success: false, error: error.message || 'Translation failed' }); } } module.exports = { createSubmission, getSubmissions, getSubmissionById, getSubmissionByBlogPost, updateSubmission, updateSubmissionStatus, addSubmissionNote, getSubmissionStatistics, getSubmissionsByPublication, exportSubmission, deleteSubmission, translateDocument };