fix: post thumbnail from linked design artefact, not old PostAttachments
Deploy / deploy (push) Successful in 12s

- 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) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-16 17:25:39 +03:00
parent 378d91648b
commit a67b2afb0d
2 changed files with 42 additions and 20 deletions
+28 -1
View File
@@ -96,4 +96,31 @@ function computeStage(composition) {
return 'copy'; 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 };
+14 -19
View File
@@ -534,6 +534,7 @@ const TEXT_COLUMNS = {
{ name: 'review_version', uidt: 'Number' }, { name: 'review_version', uidt: 'Number' },
{ name: 'caption', uidt: 'LongText' }, { name: 'caption', uidt: 'LongText' },
{ name: 'stage', uidt: 'SingleLineText' }, { name: 'stage', uidt: 'SingleLineText' },
{ name: 'thumbnail_url', uidt: 'SingleLineText' },
], ],
PostAttachments: [{ name: 'version_id', uidt: 'Number' }], PostAttachments: [{ name: 'version_id', uidt: 'Number' }],
BudgetRequests: [ BudgetRequests: [
@@ -1243,15 +1244,7 @@ app.get('/api/posts', requireAuth, async (req, res) => {
); );
} }
// Get thumbnails // Thumbnails come from post.thumbnail_url (synced from linked design artefact)
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;
}
// Collect unique IDs for name lookups // Collect unique IDs for name lookups
const brandIds = new Set(), userIds = new Set(), campaignIds = new Set(); 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, assigned_name: names[`user:${p.assigned_to_id}`] || null,
campaign_name: names[`campaign:${p.campaign_id}`] || null, campaign_name: names[`campaign:${p.campaign_id}`] || null,
creator_user_name: names[`user:${p.created_by_user_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 })), 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, sort: '-CreatedAt', limit: QUERY_LIMITS.max,
}); });
const allAttachments = await nocodb.list('PostAttachments', { // Thumbnails come from post.thumbnail_url (synced from linked design artefact)
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;
}
// Collect unique IDs for name lookups // Collect unique IDs for name lookups
const brandIds = new Set(), userIds = new Set(), trackIds = new Set(); 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, assigned_name: names[`user:${p.assigned_to_id}`] || null,
creator_user_name: names[`user:${p.created_by_user_id}`] || null, creator_user_name: names[`user:${p.created_by_user_id}`] || null,
track_name: names[`track:${p.track_id}`] || null, track_name: names[`track:${p.track_id}`] || null,
thumbnail_url: thumbMap[p.Id] || null, thumbnail_url: p.thumbnail_url || null,
}))); })));
} catch (err) { } catch (err) {
res.status(500).json({ error: 'Failed to load campaign posts' }); 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))]; const postIdsToUpdate = [...new Set([oldPostId, newPostId].filter(Boolean))];
for (const pid of postIdsToUpdate) { for (const pid of postIdsToUpdate) {
try { try {
const { getPostComposition, computeStage } = require('./post-composition'); const { getPostComposition, computeStage, syncPostThumbnail } = require('./post-composition');
const composition = await getPostComposition(pid); const composition = await getPostComposition(pid);
if (composition) { if (composition) {
await nocodb.update('Posts', pid, { stage: computeStage(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); } } catch (e) { console.error('Post stage update error:', e); }
} }
@@ -4542,6 +4531,12 @@ app.post('/api/artefacts/:id/versions/:versionId/attachments', requireAuth, dyna
...attachment, ...attachment,
url: attachment.drive_url || `/api/uploads/${attachment.filename}`, 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) { } catch (err) {
console.error('Upload attachment error:', err); console.error('Upload attachment error:', err);
res.status(500).json({ error: 'Failed to upload attachment' }); res.status(500).json({ error: 'Failed to upload attachment' });