From a66c5d9016632b2d475d0be7345f8dfd606de6ff Mon Sep 17 00:00:00 2001 From: TheFlow Date: Fri, 24 Oct 2025 10:19:33 +1300 Subject: [PATCH] fix(submissions): resolve Mongoose populate error for hybrid BlogPost model - BlogPost uses native MongoDB (not Mongoose), causing MissingSchemaError - Removed all .populate('blogPostId') calls that tried to reference non-existent Mongoose model - Manually fetch blog post data in controllers when needed - Updated getSubmissions, getSubmissionById, getSubmissionByBlogPost, exportSubmission - Updated SubmissionTracking static methods: getByStatus, getByPublication - Standalone submissions (like Le Monde) now display without errors --- .claude/session-state.json | 18 ++--- .claude/token-checkpoints.json | 2 +- src/controllers/submissions.controller.js | 92 +++++++++++++++++++---- src/models/SubmissionTracking.model.js | 4 +- 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/.claude/session-state.json b/.claude/session-state.json index 127e8605..2b8be8cb 100644 --- a/.claude/session-state.json +++ b/.claude/session-state.json @@ -43,8 +43,8 @@ "last_deliberation": null }, "FileEditHook": { - "timestamp": "2025-10-23T21:02:12.794Z", - "file": "/home/theflow/projects/tractatus/public/js/admin/blog-curation-enhanced.js", + "timestamp": "2025-10-23T21:07:21.094Z", + "file": "/home/theflow/projects/tractatus/src/models/SubmissionTracking.model.js", "result": "passed" }, "FileWriteHook": { @@ -58,25 +58,25 @@ "tokens": 30000 }, "alerts": [], - "last_updated": "2025-10-23T21:02:12.794Z", + "last_updated": "2025-10-23T21:07:21.094Z", "initialized": true, "framework_components": { "CrossReferenceValidator": { "message": 0, "tokens": 0, - "timestamp": "2025-10-23T21:02:25.161Z", - "last_validation": "2025-10-23T21:02:25.161Z", - "validations_performed": 666 + "timestamp": "2025-10-23T21:19:30.192Z", + "last_validation": "2025-10-23T21:19:30.191Z", + "validations_performed": 676 }, "BashCommandValidator": { "message": 0, "tokens": 0, "timestamp": null, - "last_validation": "2025-10-23T21:02:25.162Z", - "validations_performed": 331, + "last_validation": "2025-10-23T21:19:30.193Z", + "validations_performed": 335, "blocks_issued": 37 } }, - "action_count": 331, + "action_count": 335, "auto_compact_events": [] } \ No newline at end of file diff --git a/.claude/token-checkpoints.json b/.claude/token-checkpoints.json index 3f83ba01..d716fb67 100644 --- a/.claude/token-checkpoints.json +++ b/.claude/token-checkpoints.json @@ -23,5 +23,5 @@ ], "next_checkpoint": 50000, "overdue": false, - "last_check": "2025-10-23T20:04:43.651Z" + "last_check": "2025-10-23T21:05:39.425Z" } \ No newline at end of file diff --git a/src/controllers/submissions.controller.js b/src/controllers/submissions.controller.js index f2452e8c..5a7421a8 100644 --- a/src/controllers/submissions.controller.js +++ b/src/controllers/submissions.controller.js @@ -81,24 +81,44 @@ async function getSubmissions(req, res) { if (status) query.status = status; if (publicationId) query.publicationId = publicationId; + // NOTE: BlogPost is a native MongoDB class, not a Mongoose model, + // so we can't use .populate() for blogPostId. Fetch blog post data separately if needed. const submissions = await SubmissionTracking.find(query) - .populate({ - path: 'blogPostId', - select: 'title slug', - options: { strictPopulate: false } // Allow null blogPostId - }) .populate('createdBy', 'email') .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: submissions.length, + count: submissionsWithBlogData.length, total, - data: submissions + data: submissionsWithBlogData }); } catch (error) { console.error('[Submissions] Get submissions error:', error); @@ -118,7 +138,6 @@ async function getSubmissionById(req, res) { const { id } = req.params; const submission = await SubmissionTracking.findById(id) - .populate('blogPostId', 'title slug content') .populate('createdBy', 'email') .populate('lastUpdatedBy', 'email') .populate('notes.author', 'email'); @@ -130,9 +149,27 @@ async function getSubmissionById(req, res) { }); } + 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: submission + data: submissionObj }); } catch (error) { console.error('[Submissions] Get submission by ID error:', error); @@ -282,7 +319,6 @@ async function getSubmissionByBlogPost(req, res) { const { blogPostId } = req.params; const submission = await SubmissionTracking.findOne({ blogPostId }) - .populate('blogPostId', 'title slug content') .populate('createdBy', 'email') .populate('lastUpdatedBy', 'email'); @@ -293,9 +329,25 @@ async function getSubmissionByBlogPost(req, res) { }); } + 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: submission + data: submissionObj }); } catch (error) { console.error('[Submissions] Get by blog post error:', error); @@ -370,8 +422,7 @@ async function exportSubmission(req, res) { const { id } = req.params; const { format = 'json' } = req.query; - const submission = await SubmissionTracking.findById(id) - .populate('blogPostId', 'title content'); + const submission = await SubmissionTracking.findById(id); if (!submission) { return res.status(404).json({ @@ -383,6 +434,21 @@ async function exportSubmission(req, res) { 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, diff --git a/src/models/SubmissionTracking.model.js b/src/models/SubmissionTracking.model.js index 75b6d6d2..d1e438b2 100644 --- a/src/models/SubmissionTracking.model.js +++ b/src/models/SubmissionTracking.model.js @@ -218,20 +218,20 @@ SubmissionTrackingSchema.virtual('submissionDurationDays').get(function() { /** * Get submissions by status + * NOTE: BlogPost is a native MongoDB class, not Mongoose model, so we can't populate it */ SubmissionTrackingSchema.statics.getByStatus = async function(status) { return await this.find({ status }) - .populate('blogPostId', 'title slug') .populate('createdBy', 'email') .sort({ submittedAt: -1 }); }; /** * Get submissions for a specific publication + * NOTE: BlogPost is a native MongoDB class, not Mongoose model, so we can't populate it */ SubmissionTrackingSchema.statics.getByPublication = async function(publicationId) { return await this.find({ publicationId }) - .populate('blogPostId', 'title slug') .sort({ submittedAt: -1 }); };