feat: post approval workflow, i18n completion, and multiple fixes
All checks were successful
Deploy / deploy (push) Successful in 11s
All checks were successful
Deploy / deploy (push) Successful in 11s
- Add approval process to posts (approver multi-select, rejected status column) - Reorganize PostDetailPanel into Content, Scheduling, Approval sections - Fix save button visibility: move to fixed footer via SlidePanel footer prop - Change date picker from datetime-local to date-only - Complete Arabic translations across all panels (Header, Issues, Artefacts) - Fix artefact versioning to start empty (copyFromPrevious defaults to false) - Separate media uploads by type (image, audio, video) in PostDetailPanel - Fix team membership save when editing own profile as superadmin - Server: add approver_ids column to Posts, enrich GET/POST/PATCH responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -462,6 +462,7 @@ const TEXT_COLUMNS = {
|
||||
Comments: [{ name: 'version_number', uidt: 'Number' }],
|
||||
Issues: [{ name: 'thumbnail', uidt: 'SingleLineText' }],
|
||||
Artefacts: [{ name: 'approver_ids', uidt: 'SingleLineText' }],
|
||||
Posts: [{ name: 'approver_ids', uidt: 'SingleLineText' }],
|
||||
};
|
||||
|
||||
async function ensureTextColumns() {
|
||||
@@ -1161,6 +1162,11 @@ app.get('/api/posts', requireAuth, async (req, res) => {
|
||||
if (p.assigned_to_id) userIds.add(p.assigned_to_id);
|
||||
if (p.created_by_user_id) userIds.add(p.created_by_user_id);
|
||||
if (p.campaign_id) campaignIds.add(p.campaign_id);
|
||||
if (p.approver_ids) {
|
||||
for (const id of p.approver_ids.split(',').map(s => s.trim()).filter(Boolean)) {
|
||||
userIds.add(Number(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
const names = await batchResolveNames({
|
||||
brand: { table: 'Brands', ids: [...brandIds] },
|
||||
@@ -1168,18 +1174,22 @@ app.get('/api/posts', requireAuth, async (req, res) => {
|
||||
campaign: { table: 'Campaigns', ids: [...campaignIds] },
|
||||
});
|
||||
|
||||
res.json(filtered.map(p => ({
|
||||
...p,
|
||||
brand_id: p.brand_id,
|
||||
assigned_to: p.assigned_to_id,
|
||||
campaign_id: p.campaign_id,
|
||||
created_by_user_id: p.created_by_user_id,
|
||||
brand_name: names[`brand:${p.brand_id}`] || null,
|
||||
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,
|
||||
})));
|
||||
res.json(filtered.map(p => {
|
||||
const approverIdList = p.approver_ids ? p.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : [];
|
||||
return {
|
||||
...p,
|
||||
brand_id: p.brand_id,
|
||||
assigned_to: p.assigned_to_id,
|
||||
campaign_id: p.campaign_id,
|
||||
created_by_user_id: p.created_by_user_id,
|
||||
brand_name: names[`brand:${p.brand_id}`] || null,
|
||||
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,
|
||||
approvers: approverIdList.map(id => ({ id: Number(id), name: names[`user:${Number(id)}`] || null })),
|
||||
};
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('GET /posts error:', err);
|
||||
res.status(500).json({ error: 'Failed to load posts' });
|
||||
@@ -1187,7 +1197,7 @@ app.get('/api/posts', requireAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/posts', requireAuth, async (req, res) => {
|
||||
const { title, description, brand_id, assigned_to, status, platform, platforms, content_type, scheduled_date, notes, campaign_id } = req.body;
|
||||
const { title, description, brand_id, assigned_to, status, platform, platforms, content_type, scheduled_date, notes, campaign_id, approver_ids } = req.body;
|
||||
if (!title) return res.status(400).json({ error: 'Title is required' });
|
||||
|
||||
const platformsArr = platforms || (platform ? [platform] : []);
|
||||
@@ -1204,10 +1214,16 @@ app.post('/api/posts', requireAuth, async (req, res) => {
|
||||
brand_id: brand_id ? Number(brand_id) : null,
|
||||
assigned_to_id: assigned_to ? Number(assigned_to) : null,
|
||||
campaign_id: campaign_id ? Number(campaign_id) : null,
|
||||
approver_ids: approver_ids || null,
|
||||
created_by_user_id: req.session.userId,
|
||||
});
|
||||
|
||||
const post = await nocodb.get('Posts', created.Id);
|
||||
const approverIdList = post.approver_ids ? post.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : [];
|
||||
const approverNames = {};
|
||||
for (const id of approverIdList) {
|
||||
approverNames[id] = await getRecordName('Users', Number(id));
|
||||
}
|
||||
res.status(201).json({
|
||||
...post,
|
||||
assigned_to: post.assigned_to_id,
|
||||
@@ -1215,6 +1231,7 @@ app.post('/api/posts', requireAuth, async (req, res) => {
|
||||
assigned_name: await getRecordName('Users', post.assigned_to_id),
|
||||
campaign_name: await getRecordName('Campaigns', post.campaign_id),
|
||||
creator_user_name: await getRecordName('Users', post.created_by_user_id),
|
||||
approvers: approverIdList.map(id => ({ id: Number(id), name: approverNames[id] || null })),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Create post error:', err);
|
||||
@@ -1260,6 +1277,7 @@ app.patch('/api/posts/:id', requireAuth, requireOwnerOrRole('posts', 'superadmin
|
||||
if (req.body.brand_id !== undefined) data.brand_id = req.body.brand_id ? Number(req.body.brand_id) : null;
|
||||
if (req.body.assigned_to !== undefined) data.assigned_to_id = req.body.assigned_to ? Number(req.body.assigned_to) : null;
|
||||
if (req.body.campaign_id !== undefined) data.campaign_id = req.body.campaign_id ? Number(req.body.campaign_id) : null;
|
||||
if (req.body.approver_ids !== undefined) data.approver_ids = req.body.approver_ids || null;
|
||||
|
||||
// Publish validation
|
||||
if (req.body.status === 'published') {
|
||||
@@ -1279,6 +1297,11 @@ app.patch('/api/posts/:id', requireAuth, requireOwnerOrRole('posts', 'superadmin
|
||||
await nocodb.update('Posts', id, data);
|
||||
|
||||
const post = await nocodb.get('Posts', id);
|
||||
const approverIdList = post.approver_ids ? post.approver_ids.split(',').map(s => s.trim()).filter(Boolean) : [];
|
||||
const approverNames = {};
|
||||
for (const aid of approverIdList) {
|
||||
approverNames[aid] = await getRecordName('Users', Number(aid));
|
||||
}
|
||||
res.json({
|
||||
...post,
|
||||
assigned_to: post.assigned_to_id,
|
||||
@@ -1286,6 +1309,7 @@ app.patch('/api/posts/:id', requireAuth, requireOwnerOrRole('posts', 'superadmin
|
||||
assigned_name: await getRecordName('Users', post.assigned_to_id),
|
||||
campaign_name: await getRecordName('Campaigns', post.campaign_id),
|
||||
creator_user_name: await getRecordName('Users', post.created_by_user_id),
|
||||
approvers: approverIdList.map(id => ({ id: Number(id), name: approverNames[id] || null })),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Update post error:', err);
|
||||
|
||||
Reference in New Issue
Block a user