tractatus/src/controllers/publications.controller.js
TheFlow ff44a41930 feat(blog): add Manage Submission modal for publication tracking
Implements comprehensive submission tracking workflow for blog posts
targeting external publications. This feature enables systematic
management of submission packages and progress monitoring.

Frontend:
- Add submission-modal.js with complete modal implementation
- Modal includes publication selector (22 ranked publications)
- 4-item submission checklist (cover letter, pitch, notes, bio)
- Auto-save on blur with success indicators
- Progress bar (0-100%) tracking completion
- Requirements display per publication
- Update blog-validation.js with event handlers
- Update cache versions (HTML, service worker, version.json)

Backend:
- Add GET /api/blog/:id/submissions endpoint
- Add PUT /api/blog/:id/submissions endpoint (upsert logic)
- Implement getSubmissions and updateSubmission controllers
- Fix publications controller to use config helper functions
- Integration with SubmissionTracking MongoDB model

Version: 1.8.4

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 01:55:06 +13:00

157 lines
4.4 KiB
JavaScript

/**
* Publications Controller
* API endpoints for publication targets metadata
*/
const publicationTargets = require('../config/publication-targets.config');
/**
* GET /api/publications
* Get all publication targets with optional filtering
*/
async function getPublications(req, res) {
try {
const {
type, // letter, oped, social
tier, // premier, specialist, regional, digital
culture, // global-north, asia, developing-world
minRank,
maxRank,
language // en, de, fr, pt, zh, etc.
} = req.query;
// Get all publications
let publications = publicationTargets.getAllPublications();
// Apply filters
if (type) {
publications = publications.filter(p => p.type === type);
}
if (tier) {
publications = publications.filter(p => p.tier === tier);
}
if (culture) {
publications = publications.filter(p =>
p.culture && p.culture.includes(culture)
);
}
if (minRank) {
publications = publications.filter(p => p.rank >= parseInt(minRank, 10));
}
if (maxRank) {
publications = publications.filter(p => p.rank <= parseInt(maxRank, 10));
}
if (language) {
publications = publications.filter(p =>
p.requirements && p.requirements.language === language
);
}
// Sort by rank
publications.sort((a, b) => a.rank - b.rank);
res.json({
success: true,
count: publications.length,
data: publications
});
} catch (error) {
console.error('[Publications] Get publications error:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch publications'
});
}
}
/**
* GET /api/publications/:id
* Get specific publication by ID
*/
async function getPublicationById(req, res) {
try {
const { id } = req.params;
const publication = publicationTargets.getPublicationById(id);
if (!publication) {
return res.status(404).json({
success: false,
error: 'Publication not found'
});
}
res.json({
success: true,
data: publication
});
} catch (error) {
console.error('[Publications] Get publication by ID error:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch publication'
});
}
}
/**
* GET /api/publications/summary
* Get summary statistics about publications
*/
async function getPublicationsSummary(req, res) {
try {
const publications = publicationTargets.getAllPublications();
// Calculate summary statistics
const summary = {
total: publications.length,
byType: {
letter: publications.filter(p => p.type === 'letter').length,
oped: publications.filter(p => p.type === 'oped').length,
social: publications.filter(p => p.type === 'social').length
},
byTier: {
premier: publications.filter(p => p.tier === 'premier').length,
specialist: publications.filter(p => p.tier === 'specialist').length,
regional: publications.filter(p => p.tier === 'regional').length,
digital: publications.filter(p => p.tier === 'digital').length
},
byCulture: {
'global-north': publications.filter(p => p.culture && p.culture.includes('global-north')).length,
'asia': publications.filter(p => p.culture && p.culture.includes('asia')).length,
'developing-world': publications.filter(p => p.culture && p.culture.includes('developing-world')).length
},
byLanguage: {
en: publications.filter(p => !p.requirements?.language || p.requirements.language === 'en').length,
de: publications.filter(p => p.requirements?.language === 'de').length,
fr: publications.filter(p => p.requirements?.language === 'fr').length,
pt: publications.filter(p => p.requirements?.language === 'pt').length,
zh: publications.filter(p => p.requirements?.language === 'zh').length
},
verified: publications.filter(p => p.verified).length,
unverified: publications.filter(p => !p.verified).length
};
res.json({
success: true,
data: summary
});
} catch (error) {
console.error('[Publications] Get summary error:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch publication summary'
});
}
}
module.exports = {
getPublications,
getPublicationById,
getPublicationsSummary
};