tractatus/src/controllers/submissions.controller.js
TheFlow 40601f7d27 refactor(lint): fix code style and unused variables across src/
- Fixed unused function parameters by prefixing with underscore
- Removed unused imports and variables
- Applied eslint --fix for automatic style fixes
  - Property shorthand
  - String template literals
  - Prefer const over let where appropriate
  - Spacing and formatting

Reduces lint errors from 108+ to 78 (61 unused vars, 17 other issues)

Related to CI lint failures in previous commit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 20:15:26 +13:00

597 lines
15 KiB
JavaScript

/**
* 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
};