Add feature set import export
This commit is contained in:
@@ -56,6 +56,29 @@ function scoreIdea({ impact, effort, confidence, urgency }) {
|
||||
return Number((((i * 2.4) + (c * 1.2) + (u * 1.4)) / Math.max(1, e)).toFixed(2));
|
||||
}
|
||||
|
||||
function ideaDataFromInput(input = {}, defaults = {}) {
|
||||
const title = cleanText(input.title || input.name, 180);
|
||||
if (!title) throw Object.assign(new Error('Every feature needs a title.'), { code: 400 });
|
||||
const data = {
|
||||
title,
|
||||
description: cleanMultiline(input.description || input.brief || '', 5000),
|
||||
source: cleanText(input.source || defaults.source || 'import', 40),
|
||||
sourceName: cleanText(input.sourceName || input.owner || defaults.sourceName || 'Prioritix feature set', 80),
|
||||
status: cleanText(input.status || defaults.status || 'inbox', 40),
|
||||
milestoneId: cleanText(input.milestoneId || input.milestone || defaults.milestoneId || 'inbox', 64),
|
||||
impact: clampInt(input.impact, defaults.impact ?? 5),
|
||||
effort: clampInt(input.effort, defaults.effort ?? 5, 1, 10),
|
||||
confidence: clampInt(input.confidence, defaults.confidence ?? 6),
|
||||
urgency: clampInt(input.urgency, defaults.urgency ?? 5),
|
||||
rank: clampInt(input.rank, defaults.rank ?? 0, -100000, 100000),
|
||||
labels: encodeList(input.labels || input.category || input.categories || []),
|
||||
notes: cleanMultiline(input.notes || '', 4000),
|
||||
archived: Boolean(input.archived ?? false),
|
||||
};
|
||||
data.score = scoreIdea(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function publicIdea(row) {
|
||||
return {
|
||||
id: row.$id,
|
||||
@@ -174,30 +197,31 @@ app.get('/api/bootstrap', async (_req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/ideas', requireAgent, async (req, res) => {
|
||||
const title = cleanText(req.body.title, 180);
|
||||
if (!title) return res.status(400).json({ error: 'title is required' });
|
||||
const data = {
|
||||
title,
|
||||
description: cleanMultiline(req.body.description, 5000),
|
||||
source: cleanText(req.body.source || 'human', 40),
|
||||
sourceName: cleanText(req.body.sourceName || req.body.agent || '', 80),
|
||||
status: cleanText(req.body.status || 'inbox', 40),
|
||||
milestoneId: cleanText(req.body.milestoneId || 'inbox', 64),
|
||||
impact: clampInt(req.body.impact, 5),
|
||||
effort: clampInt(req.body.effort, 5, 1, 10),
|
||||
confidence: clampInt(req.body.confidence, 6),
|
||||
urgency: clampInt(req.body.urgency, 5),
|
||||
rank: clampInt(req.body.rank, 0, -100000, 100000),
|
||||
labels: encodeList(req.body.labels),
|
||||
notes: cleanMultiline(req.body.notes, 4000),
|
||||
archived: false,
|
||||
};
|
||||
data.score = scoreIdea(data);
|
||||
const data = ideaDataFromInput(req.body, { source: 'human', status: 'inbox', milestoneId: 'inbox' });
|
||||
const row = assertRow(await tables.createRow({ databaseId, tableId: ideasTableId, rowId: ID.unique(), data }));
|
||||
await logActivity('idea.created', `Captured “${title}”`, row.$id, data.source);
|
||||
await logActivity('idea.created', `Captured “${data.title}”`, row.$id, data.source);
|
||||
res.status(201).json(publicIdea(row));
|
||||
});
|
||||
|
||||
app.post('/api/feature-set/import', requireAgent, async (req, res) => {
|
||||
const features = Array.isArray(req.body.features) ? req.body.features.slice(0, 100) : [];
|
||||
if (!features.length) return res.status(400).json({ error: 'Feature set must include a non-empty features array.' });
|
||||
const defaults = req.body.defaults && typeof req.body.defaults === 'object' ? req.body.defaults : {};
|
||||
const created = [];
|
||||
const errors = [];
|
||||
for (const [index, feature] of features.entries()) {
|
||||
try {
|
||||
const data = ideaDataFromInput(feature, { source: 'import', sourceName: req.body.name || 'Prioritix feature set', ...defaults });
|
||||
const row = assertRow(await tables.createRow({ databaseId, tableId: ideasTableId, rowId: ID.unique(), data }));
|
||||
created.push(publicIdea(row));
|
||||
} catch (error) {
|
||||
errors.push({ index, title: cleanText(feature?.title || feature?.name || '', 180), error: error.message });
|
||||
}
|
||||
}
|
||||
if (created.length) await logActivity('feature_set.imported', `Imported ${created.length} feature${created.length === 1 ? '' : 's'}`, '', req.body.name || 'feature-set-v1');
|
||||
res.status(created.length ? 201 : 400).json({ ok: errors.length === 0, imported: created.length, created, errors });
|
||||
});
|
||||
|
||||
app.patch('/api/ideas/:id', requireAgent, async (req, res) => {
|
||||
const allowed = ['title', 'description', 'source', 'sourceName', 'status', 'milestoneId', 'impact', 'effort', 'confidence', 'urgency', 'rank', 'labels', 'notes', 'archived'];
|
||||
const data = {};
|
||||
|
||||
Reference in New Issue
Block a user