From a67b2afb0d7bb2845806140fb9ce51d901048841 Mon Sep 17 00:00:00 2001 From: fahed Date: Mon, 16 Mar 2026 17:25:39 +0300 Subject: [PATCH] fix: post thumbnail from linked design artefact, not old PostAttachments - Post.thumbnail_url now synced from linked design artefact's first attachment - syncPostThumbnail() called on: artefact attachment upload, artefact link/unlink - Removed old PostAttachments-based thumbMap from GET /posts and GET /campaigns/:id/posts - Added thumbnail_url to Posts TEXT_COLUMNS - Caption link picker filters by copy_type='caption', body by copy_type='body' Co-Authored-By: Claude Opus 4.6 (1M context) --- server/post-composition.js | 29 ++++++++++++++++++++++++++++- server/server.js | 33 ++++++++++++++------------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/server/post-composition.js b/server/post-composition.js index bffceb2..253b772 100644 --- a/server/post-composition.js +++ b/server/post-composition.js @@ -96,4 +96,31 @@ function computeStage(composition) { return 'copy'; } -module.exports = { getPostComposition, computeStage }; +// Sync the post's thumbnail_url from its linked design artefact +async function syncPostThumbnail(postId) { + try { + const artefacts = await nocodb.list('Artefacts', { + where: `(post_id,eq,${postId})`, limit: 100, + }); + const design = artefacts.find(a => (a.type || 'design') === 'design'); + let thumb = null; + if (design) { + thumb = design.thumbnail_url || null; + if (!thumb) { + const versions = await nocodb.list('ArtefactVersions', { where: `(artefact_id,eq,${design.Id})`, sort: '-version_number', limit: 1 }); + if (versions.length > 0) { + const attachments = await nocodb.list('ArtefactAttachments', { where: `(version_id,eq,${versions[0].Id})`, limit: 1 }); + if (attachments.length > 0) { + const att = attachments[0]; + thumb = att.drive_url || (att.filename ? `/api/uploads/${att.filename}` : null); + } + } + } + } + await nocodb.update('Posts', Number(postId), { thumbnail_url: thumb || null }); + } catch (e) { + console.error('syncPostThumbnail error:', e); + } +} + +module.exports = { getPostComposition, computeStage, syncPostThumbnail }; diff --git a/server/server.js b/server/server.js index f07c524..038aaa6 100644 --- a/server/server.js +++ b/server/server.js @@ -534,6 +534,7 @@ const TEXT_COLUMNS = { { name: 'review_version', uidt: 'Number' }, { name: 'caption', uidt: 'LongText' }, { name: 'stage', uidt: 'SingleLineText' }, + { name: 'thumbnail_url', uidt: 'SingleLineText' }, ], PostAttachments: [{ name: 'version_id', uidt: 'Number' }], BudgetRequests: [ @@ -1243,15 +1244,7 @@ app.get('/api/posts', requireAuth, async (req, res) => { ); } - // Get thumbnails - const allAttachments = await nocodb.list('PostAttachments', { - where: "(mime_type,like,image/%)", - limit: QUERY_LIMITS.max, - }); - const thumbMap = {}; - for (const att of allAttachments) { - if (att.post_id && !thumbMap[att.post_id]) thumbMap[att.post_id] = att.url; - } + // Thumbnails come from post.thumbnail_url (synced from linked design artefact) // Collect unique IDs for name lookups const brandIds = new Set(), userIds = new Set(), campaignIds = new Set(); @@ -1284,7 +1277,7 @@ app.get('/api/posts', requireAuth, async (req, res) => { assigned_name: names[`user:${p.assigned_to_id}`] || null, campaign_name: names[`campaign:${p.campaign_id}`] || null, creator_user_name: names[`user:${p.created_by_user_id}`] || null, - thumbnail_url: thumbMap[p.Id] || null, + thumbnail_url: p.thumbnail_url || null, approvers: approverIdList.map(id => ({ id: Number(id), name: names[`user:${Number(id)}`] || null })), }; })); @@ -3032,13 +3025,7 @@ app.get('/api/campaigns/:id/posts', requireAuth, async (req, res) => { sort: '-CreatedAt', limit: QUERY_LIMITS.max, }); - const allAttachments = await nocodb.list('PostAttachments', { - where: "(mime_type,like,image/%)", limit: QUERY_LIMITS.max, - }); - const thumbMap = {}; - for (const att of allAttachments) { - if (att.post_id && !thumbMap[att.post_id]) thumbMap[att.post_id] = att.url; - } + // Thumbnails come from post.thumbnail_url (synced from linked design artefact) // Collect unique IDs for name lookups const brandIds = new Set(), userIds = new Set(), trackIds = new Set(); @@ -3063,7 +3050,7 @@ app.get('/api/campaigns/:id/posts', requireAuth, async (req, res) => { assigned_name: names[`user:${p.assigned_to_id}`] || null, creator_user_name: names[`user:${p.created_by_user_id}`] || null, track_name: names[`track:${p.track_id}`] || null, - thumbnail_url: thumbMap[p.Id] || null, + thumbnail_url: p.thumbnail_url || null, }))); } catch (err) { res.status(500).json({ error: 'Failed to load campaign posts' }); @@ -4135,11 +4122,13 @@ app.patch('/api/artefacts/:id', requireAuth, async (req, res) => { const postIdsToUpdate = [...new Set([oldPostId, newPostId].filter(Boolean))]; for (const pid of postIdsToUpdate) { try { - const { getPostComposition, computeStage } = require('./post-composition'); + const { getPostComposition, computeStage, syncPostThumbnail } = require('./post-composition'); const composition = await getPostComposition(pid); if (composition) { await nocodb.update('Posts', pid, { stage: computeStage(composition) }); } + // Sync thumbnail if design artefact was linked/unlinked + if (updatedArtefact?.type === 'design') syncPostThumbnail(pid).catch(() => {}); } catch (e) { console.error('Post stage update error:', e); } } @@ -4542,6 +4531,12 @@ app.post('/api/artefacts/:id/versions/:versionId/attachments', requireAuth, dyna ...attachment, url: attachment.drive_url || `/api/uploads/${attachment.filename}`, }); + + // Sync post thumbnail if this is a design artefact linked to a post + if (artefact.post_id && (artefact.type === 'design')) { + const { syncPostThumbnail } = require('./post-composition'); + syncPostThumbnail(artefact.post_id).catch(() => {}); + } } catch (err) { console.error('Upload attachment error:', err); res.status(500).json({ error: 'Failed to upload attachment' });