properly fixed user upto [200~Business Context Questions
This commit is contained in:
parent
909d446d77
commit
49a19447dc
@ -375,6 +375,19 @@ app.use('/api/requirements',
|
|||||||
serviceRouter.createServiceProxy(serviceTargets.REQUIREMENT_PROCESSOR_URL, 'requirement-processor')
|
serviceRouter.createServiceProxy(serviceTargets.REQUIREMENT_PROCESSOR_URL, 'requirement-processor')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Questions (Requirement Processor) - expose /api/questions via gateway
|
||||||
|
// Rewrites /api/questions/* -> /api/v1/* at the Requirement Processor
|
||||||
|
app.use('/api/questions',
|
||||||
|
createServiceLimiter(300),
|
||||||
|
// Allow unauthenticated access for generating questions (public step in builder)
|
||||||
|
(req, res, next) => next(),
|
||||||
|
serviceRouter.createServiceProxy(
|
||||||
|
serviceTargets.REQUIREMENT_PROCESSOR_URL,
|
||||||
|
'requirement-processor-questions',
|
||||||
|
{ pathRewrite: { '^/api/questions': '/api/v1' } }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// Tech Stack Selector Service
|
// Tech Stack Selector Service
|
||||||
app.use('/api/tech-stack',
|
app.use('/api/tech-stack',
|
||||||
createServiceLimiter(200),
|
createServiceLimiter(200),
|
||||||
|
|||||||
@ -42,13 +42,55 @@ class CustomFeature {
|
|||||||
|
|
||||||
static async create(data) {
|
static async create(data) {
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
// Normalize JSONB-like fields to ensure valid JSON is sent to PG
|
||||||
|
const normalizeJsonb = (value) => {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try { return JSON.parse(value); } catch {
|
||||||
|
// Accept plain strings by storing as JSON string (quoted)
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
const toJsonbSafe = (value) => {
|
||||||
|
try {
|
||||||
|
const v = normalizeJsonb(value);
|
||||||
|
// Only objects/arrays/strings are JSON-serializable; numbers/booleans fine too
|
||||||
|
// But if we get something unexpected, fallback to null
|
||||||
|
if (v === null || v === undefined) return null;
|
||||||
|
// If array, coerce entries away from undefined
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v.map((item) => (item === undefined ? null : item));
|
||||||
|
}
|
||||||
|
// Plain object or primitive
|
||||||
|
return v;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const businessRules = toJsonbSafe(data.business_rules);
|
||||||
|
const technicalRequirements = toJsonbSafe(data.technical_requirements);
|
||||||
|
// Debug logging to trace JSON payloads that will be cast to jsonb
|
||||||
|
try {
|
||||||
|
console.log('🧪 [CustomFeature.create] JSON payloads:', {
|
||||||
|
businessRulesPreview: businessRules === null ? null : JSON.stringify(businessRules).slice(0, 200),
|
||||||
|
technicalRequirementsPreview: technicalRequirements === null ? null : JSON.stringify(technicalRequirements).slice(0, 200)
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO custom_features (
|
INSERT INTO custom_features (
|
||||||
id, template_id, template_type, name, description, complexity,
|
id, template_id, template_type, name, description, complexity,
|
||||||
business_rules, technical_requirements, approved, usage_count, created_by_user_session,
|
business_rules, technical_requirements, approved, usage_count, created_by_user_session,
|
||||||
status, admin_notes, admin_reviewed_at, admin_reviewed_by, canonical_feature_id, similarity_score,
|
status, admin_notes, admin_reviewed_at, admin_reviewed_by, canonical_feature_id, similarity_score,
|
||||||
created_at, updated_at
|
created_at, updated_at
|
||||||
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,DEFAULT,DEFAULT)
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,
|
||||||
|
$7::jsonb,$8::jsonb,$9,$10,$11,
|
||||||
|
$12,$13,$14,$15,$16,$17,
|
||||||
|
DEFAULT,DEFAULT
|
||||||
|
)
|
||||||
RETURNING *
|
RETURNING *
|
||||||
`;
|
`;
|
||||||
const values = [
|
const values = [
|
||||||
@ -58,8 +100,8 @@ class CustomFeature {
|
|||||||
data.name,
|
data.name,
|
||||||
data.description || null,
|
data.description || null,
|
||||||
data.complexity,
|
data.complexity,
|
||||||
data.business_rules || null,
|
(() => { try { return businessRules === null ? null : JSON.stringify(businessRules); } catch { return null; } })(),
|
||||||
data.technical_requirements || null,
|
(() => { try { return technicalRequirements === null ? null : JSON.stringify(technicalRequirements); } catch { return null; } })(),
|
||||||
data.approved ?? false,
|
data.approved ?? false,
|
||||||
data.usage_count ?? 1,
|
data.usage_count ?? 1,
|
||||||
data.created_by_user_session || null,
|
data.created_by_user_session || null,
|
||||||
@ -75,6 +117,24 @@ class CustomFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async update(id, updates) {
|
static async update(id, updates) {
|
||||||
|
// Normalize JSONB-like fields before constructing the query
|
||||||
|
const normalizeJsonb = (value) => {
|
||||||
|
if (value === undefined) return undefined;
|
||||||
|
if (value === null || value === '') return null;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try { return JSON.parse(value); } catch {
|
||||||
|
// Accept plain strings by storing as JSON string (quoted)
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
if (updates && Object.prototype.hasOwnProperty.call(updates, 'business_rules')) {
|
||||||
|
updates.business_rules = normalizeJsonb(updates.business_rules);
|
||||||
|
}
|
||||||
|
if (updates && Object.prototype.hasOwnProperty.call(updates, 'technical_requirements')) {
|
||||||
|
updates.technical_requirements = normalizeJsonb(updates.technical_requirements);
|
||||||
|
}
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
@ -85,10 +145,16 @@ class CustomFeature {
|
|||||||
];
|
];
|
||||||
for (const k of allowed) {
|
for (const k of allowed) {
|
||||||
if (updates[k] !== undefined) {
|
if (updates[k] !== undefined) {
|
||||||
|
if (k === 'business_rules' || k === 'technical_requirements') {
|
||||||
|
fields.push(`${k} = $${idx++}::jsonb`);
|
||||||
|
const v = updates[k] === null ? null : JSON.stringify(updates[k]);
|
||||||
|
values.push(v);
|
||||||
|
} else {
|
||||||
fields.push(`${k} = $${idx++}`);
|
fields.push(`${k} = $${idx++}`);
|
||||||
values.push(updates[k]);
|
values.push(updates[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (fields.length === 0) return await CustomFeature.getById(id);
|
if (fields.length === 0) return await CustomFeature.getById(id);
|
||||||
const query = `UPDATE custom_features SET ${fields.join(', ')}, updated_at = NOW() WHERE id = $${idx} RETURNING *`;
|
const query = `UPDATE custom_features SET ${fields.join(', ')}, updated_at = NOW() WHERE id = $${idx} RETURNING *`;
|
||||||
values.push(id);
|
values.push(id);
|
||||||
|
|||||||
@ -367,25 +367,10 @@ router.put('/:id', async (req, res) => {
|
|||||||
|
|
||||||
let updated;
|
let updated;
|
||||||
if (isCustomFeature) {
|
if (isCustomFeature) {
|
||||||
// Update custom feature
|
// Update custom feature (only in custom_features table)
|
||||||
updated = await CustomFeature.update(id, updateData);
|
updated = await CustomFeature.update(id, updateData);
|
||||||
|
|
||||||
// Mirror update into template_features where feature_id = `custom_<id>`
|
|
||||||
try {
|
|
||||||
const featureId = `custom_${id}`;
|
|
||||||
const mirroredExisting = await Feature.getByFeatureId(existing.template_id, featureId);
|
|
||||||
if (mirroredExisting) {
|
|
||||||
await Feature.update(mirroredExisting.id, {
|
|
||||||
name: updateData.name ?? mirroredExisting.name,
|
|
||||||
description: updateData.description ?? mirroredExisting.description,
|
|
||||||
complexity: updateData.complexity ?? mirroredExisting.complexity,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (mirrorErr) {
|
|
||||||
console.error('Failed to mirror custom feature update:', mirrorErr.message);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Update regular feature
|
// Update regular feature (only in template_features table)
|
||||||
updated = await Feature.update(id, updateData);
|
updated = await Feature.update(id, updateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +412,28 @@ router.delete('/:id', async (req, res) => {
|
|||||||
router.post('/custom', async (req, res) => {
|
router.post('/custom', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = req.body || {}
|
const data = req.body || {}
|
||||||
|
// Normalize and validate JSON-like fields to avoid invalid input syntax for type json
|
||||||
|
const tryParseJson = (value) => {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
// Only attempt to parse if it looks like JSON; otherwise pass through as plain string
|
||||||
|
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
||||||
|
try { return JSON.parse(trimmed); } catch {
|
||||||
|
// If it looks like JSON but fails to parse, fallback to null so we don't send invalid jsonb
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow plain strings; model will stringify safely for jsonb columns
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be lenient: normalize but do not reject if strings are not valid JSON
|
||||||
|
if (data.business_rules !== undefined) data.business_rules = tryParseJson(data.business_rules)
|
||||||
|
if (data.logic_rules !== undefined) data.logic_rules = tryParseJson(data.logic_rules)
|
||||||
|
if (data.technical_requirements !== undefined) data.technical_requirements = tryParseJson(data.technical_requirements)
|
||||||
console.log('🔍 Custom feature creation request:', { template_id: data.template_id, name: data.name, complexity: data.complexity, description: data.description })
|
console.log('🔍 Custom feature creation request:', { template_id: data.template_id, name: data.name, complexity: data.complexity, description: data.description })
|
||||||
const required = ['template_id', 'name', 'complexity']
|
const required = ['template_id', 'name', 'complexity']
|
||||||
for (const f of required) {
|
for (const f of required) {
|
||||||
@ -466,6 +473,9 @@ router.post('/custom', async (req, res) => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
complexity: data.complexity,
|
complexity: data.complexity,
|
||||||
|
// Persist incoming structured fields on the row as well
|
||||||
|
business_rules: data.business_rules ?? (data.logic_rules ? [{ requirement: 'Aggregated', rules: data.logic_rules }] : null),
|
||||||
|
technical_requirements: data.technical_requirements ?? null,
|
||||||
approved: false,
|
approved: false,
|
||||||
usage_count: 1,
|
usage_count: 1,
|
||||||
created_by_user_session: data.created_by_user_session,
|
created_by_user_session: data.created_by_user_session,
|
||||||
@ -476,27 +486,27 @@ router.post('/custom', async (req, res) => {
|
|||||||
|
|
||||||
try { await AdminNotification.notifyNewFeature(created.id, created.name); } catch (e) { console.error('⚠️ Failed to create admin notification:', e.message); }
|
try { await AdminNotification.notifyNewFeature(created.id, created.name); } catch (e) { console.error('⚠️ Failed to create admin notification:', e.message); }
|
||||||
|
|
||||||
try {
|
// Custom features are only stored in custom_features table, not mirrored to template_features
|
||||||
await Feature.create({
|
|
||||||
template_id: data.template_id,
|
|
||||||
feature_id: data.id,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
feature_type: 'custom',
|
|
||||||
complexity: data.complexity,
|
|
||||||
display_order: 999,
|
|
||||||
is_default: false,
|
|
||||||
created_by_user: true
|
|
||||||
})
|
|
||||||
} catch (mirrorErr) { console.error('Failed to mirror custom feature into template_features:', mirrorErr.message) }
|
|
||||||
|
|
||||||
// Persist aggregated rules
|
// Persist aggregated rules (using actual custom feature ID)
|
||||||
try {
|
try {
|
||||||
// Prefer structured business_rules; fallback to flat logic_rules
|
// Prefer structured business_rules; fallback to flat logic_rules
|
||||||
const rules = (data.business_rules ?? data.logic_rules ?? []);
|
const rules = (data.business_rules ?? data.logic_rules ?? []);
|
||||||
await FeatureBusinessRules.upsert(data.template_id, `custom_${created.id}`, rules);
|
if (Array.isArray(rules) && rules.length > 0) {
|
||||||
|
await FeatureBusinessRules.upsert(data.template_id, created.id, rules);
|
||||||
|
} else {
|
||||||
|
// If nothing to upsert, keep a copy on the custom_features row
|
||||||
|
try { await CustomFeature.update(created.id, { business_rules: rules }); } catch {}
|
||||||
|
}
|
||||||
} catch (ruleErr) {
|
} catch (ruleErr) {
|
||||||
console.error('⚠️ Failed to persist custom feature business rules:', ruleErr.message);
|
// Fallback: persist on the custom_features row so edit can still load them
|
||||||
|
console.error('⚠️ Failed to persist custom feature business rules (feature_business_rules). Falling back to custom_features.business_rules:', ruleErr.message);
|
||||||
|
try {
|
||||||
|
const fallbackRules = (data.business_rules ?? data.logic_rules ?? []);
|
||||||
|
await CustomFeature.update(created.id, { business_rules: fallbackRules });
|
||||||
|
} catch (fallbackErr) {
|
||||||
|
console.error('⚠️ Fallback save to custom_features.business_rules also failed:', fallbackErr.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = { success: true, data: created, message: `Custom feature '${created.name}' created successfully and submitted for admin review` };
|
const response = { success: true, data: created, message: `Custom feature '${created.name}' created successfully and submitted for admin review` };
|
||||||
@ -545,20 +555,8 @@ router.put('/custom/:id', async (req, res) => {
|
|||||||
if (!existing) return res.status(404).json({ success: false, error: 'Not found' });
|
if (!existing) return res.status(404).json({ success: false, error: 'Not found' });
|
||||||
const updates = req.body || {}
|
const updates = req.body || {}
|
||||||
const updated = await CustomFeature.update(id, updates);
|
const updated = await CustomFeature.update(id, updates);
|
||||||
// Mirror update into template_features where feature_id = `custom_<id>`
|
|
||||||
try {
|
// Custom features are only stored in custom_features table, no mirroring needed
|
||||||
const featureId = `custom_${id}`
|
|
||||||
const mirroredExisting = await Feature.getByFeatureId(existing.template_id, featureId)
|
|
||||||
if (mirroredExisting) {
|
|
||||||
await Feature.update(mirroredExisting.id, {
|
|
||||||
name: updates.name ?? mirroredExisting.name,
|
|
||||||
description: updates.description ?? mirroredExisting.description,
|
|
||||||
complexity: updates.complexity ?? mirroredExisting.complexity,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (mirrorErr) {
|
|
||||||
console.error('Failed to mirror custom feature update:', mirrorErr.message)
|
|
||||||
}
|
|
||||||
res.json({ success: true, data: updated, message: `Custom feature '${updated.name}' updated successfully` });
|
res.json({ success: true, data: updated, message: `Custom feature '${updated.name}' updated successfully` });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).json({ success: false, error: 'Failed to update custom feature', message: e.message });
|
res.status(500).json({ success: false, error: 'Failed to update custom feature', message: e.message });
|
||||||
@ -569,46 +567,24 @@ router.put('/custom/:id', async (req, res) => {
|
|||||||
router.delete('/custom/:id', async (req, res) => {
|
router.delete('/custom/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
// const rawId = String(id).replace(/^custom_/, '')
|
|
||||||
|
|
||||||
// Try deleting from custom_features first
|
// Find and delete from custom_features table
|
||||||
let existing = await CustomFeature.getById(id);
|
const existing = await CustomFeature.getById(id);
|
||||||
if (existing) {
|
if (!existing) {
|
||||||
await CustomFeature.delete(id);
|
return res.status(404).json({ success: false, error: 'Not found', message: 'Custom feature not found' });
|
||||||
// Remove mirrored template_features with feature_id = `custom_<id>`
|
|
||||||
try {
|
|
||||||
const featureId = id
|
|
||||||
const mirroredExisting = await Feature.getByFeatureId(existing.template_id, featureId)
|
|
||||||
if (mirroredExisting) {
|
|
||||||
await Feature.delete(mirroredExisting.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete the custom feature
|
||||||
|
await CustomFeature.delete(id);
|
||||||
|
|
||||||
// Cleanup business rules if present
|
// Cleanup business rules if present
|
||||||
try {
|
try {
|
||||||
await database.query('DELETE FROM feature_business_rules WHERE template_id = $1 AND feature_id = $2', [existing.template_id, featureId])
|
await database.query('DELETE FROM feature_business_rules WHERE template_id = $1 AND feature_id = $2', [existing.template_id, id])
|
||||||
} catch (cleanupErr) { console.error('Failed to cleanup business rules:', cleanupErr.message) }
|
} catch (cleanupErr) {
|
||||||
} catch (mirrorErr) {
|
console.error('Failed to cleanup business rules:', cleanupErr.message)
|
||||||
console.error('Failed to mirror custom feature delete:', mirrorErr.message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({ success: true, message: `Custom feature '${existing.name}' deleted successfully` });
|
return res.json({ success: true, message: `Custom feature '${existing.name}' deleted successfully` });
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: handle case where only mirrored template_features exists or client sent prefixed id
|
|
||||||
try {
|
|
||||||
const prefixed = id.startsWith('custom_') ? id : `custom_${rawId}`
|
|
||||||
const tf = await database.query('SELECT id, template_id, name FROM template_features WHERE feature_id = $1', [prefixed])
|
|
||||||
if (tf.rows.length > 0) {
|
|
||||||
const row = tf.rows[0]
|
|
||||||
await Feature.delete(row.id)
|
|
||||||
try {
|
|
||||||
await database.query('DELETE FROM feature_business_rules WHERE template_id = $1 AND feature_id = $2', [row.template_id, prefixed])
|
|
||||||
} catch (cleanupErr) { console.error('Failed to cleanup business rules:', cleanupErr.message) }
|
|
||||||
return res.json({ success: true, message: `Mirrored feature '${row.name}' deleted successfully` })
|
|
||||||
}
|
|
||||||
} catch (fallbackErr) {
|
|
||||||
console.error('Fallback delete check failed:', fallbackErr.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(404).json({ success: false, error: 'Not found', message: 'Custom feature not found' });
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).json({ success: false, error: 'Failed to delete custom feature', message: e.message });
|
res.status(500).json({ success: false, error: 'Failed to delete custom feature', message: e.message });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -512,40 +512,85 @@ router.get('/:id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/f
|
|||||||
|
|
||||||
console.log(`✅ Template found: ${templateCheck.rows[0].title} (${templateCheck.rows[0].template_type})`);
|
console.log(`✅ Template found: ${templateCheck.rows[0].title} (${templateCheck.rows[0].template_type})`);
|
||||||
|
|
||||||
let features = [];
|
// Fetch features from both tables for proper separation
|
||||||
|
console.log('📋 Fetching features from both template_features and custom_features tables');
|
||||||
|
|
||||||
if (templateCheck.rows[0].template_type === 'custom') {
|
// Get default/suggested features from template_features table
|
||||||
// For custom templates, get features from custom_features table
|
const defaultFeatures = await Feature.getByTemplateId(id);
|
||||||
console.log('📋 Fetching features from custom_features table for custom template');
|
console.log(`📊 Found ${defaultFeatures.length} default/suggested features`);
|
||||||
const customFeaturesQuery = `
|
|
||||||
|
// Get custom features from custom_features table with business rules (if table exists)
|
||||||
|
// Some environments may not have run the feature_business_rules migration yet. Probe first.
|
||||||
|
const fbrExistsProbe = await database.query("SELECT to_regclass('public.feature_business_rules') AS tbl");
|
||||||
|
const hasFbrTable = !!(fbrExistsProbe.rows && fbrExistsProbe.rows[0] && fbrExistsProbe.rows[0].tbl);
|
||||||
|
|
||||||
|
const customFeaturesQuery = hasFbrTable
|
||||||
|
? `
|
||||||
SELECT
|
SELECT
|
||||||
cf.id,
|
cf.id,
|
||||||
cf.template_id,
|
cf.template_id,
|
||||||
cf.name,
|
cf.name,
|
||||||
cf.description,
|
cf.description,
|
||||||
cf.complexity,
|
cf.complexity,
|
||||||
|
cf.business_rules,
|
||||||
|
cf.technical_requirements,
|
||||||
'custom' as feature_type,
|
'custom' as feature_type,
|
||||||
cf.created_at,
|
cf.created_at,
|
||||||
cf.updated_at,
|
cf.updated_at,
|
||||||
cf.status,
|
cf.status,
|
||||||
cf.approved
|
cf.approved,
|
||||||
|
cf.usage_count,
|
||||||
|
0 as user_rating,
|
||||||
|
false as is_default,
|
||||||
|
true as created_by_user,
|
||||||
|
fbr.business_rules as additional_business_rules
|
||||||
|
FROM custom_features cf
|
||||||
|
LEFT JOIN feature_business_rules fbr
|
||||||
|
ON cf.template_id = fbr.template_id
|
||||||
|
AND (
|
||||||
|
fbr.feature_id = (cf.id::text)
|
||||||
|
OR fbr.feature_id = ('custom_' || cf.id::text)
|
||||||
|
)
|
||||||
|
WHERE cf.template_id = $1
|
||||||
|
ORDER BY cf.created_at DESC
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
SELECT
|
||||||
|
cf.id,
|
||||||
|
cf.template_id,
|
||||||
|
cf.name,
|
||||||
|
cf.description,
|
||||||
|
cf.complexity,
|
||||||
|
cf.business_rules,
|
||||||
|
cf.technical_requirements,
|
||||||
|
'custom' as feature_type,
|
||||||
|
cf.created_at,
|
||||||
|
cf.updated_at,
|
||||||
|
cf.status,
|
||||||
|
cf.approved,
|
||||||
|
cf.usage_count,
|
||||||
|
0 as user_rating,
|
||||||
|
false as is_default,
|
||||||
|
true as created_by_user,
|
||||||
|
NULL::jsonb as additional_business_rules
|
||||||
FROM custom_features cf
|
FROM custom_features cf
|
||||||
WHERE cf.template_id = $1
|
WHERE cf.template_id = $1
|
||||||
ORDER BY cf.created_at DESC
|
ORDER BY cf.created_at DESC
|
||||||
`;
|
`;
|
||||||
const customFeaturesResult = await database.query(customFeaturesQuery, [id]);
|
const customFeaturesResult = await database.query(customFeaturesQuery, [id]);
|
||||||
features = customFeaturesResult.rows;
|
const customFeatures = customFeaturesResult.rows;
|
||||||
} else {
|
console.log(`📊 Found ${customFeatures.length} custom features`);
|
||||||
// For default templates, get features from template_features table
|
|
||||||
console.log('📋 Fetching features from template_features table for default template');
|
// Combine both types of features
|
||||||
features = await Feature.getByTemplateId(id);
|
const features = [...defaultFeatures, ...customFeatures];
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: features,
|
data: features,
|
||||||
count: features.length,
|
count: features.length,
|
||||||
message: `Found ${features.length} features for ${templateCheck.rows[0].template_type} template`,
|
defaultFeaturesCount: defaultFeatures.length,
|
||||||
|
customFeaturesCount: customFeatures.length,
|
||||||
|
message: `Found ${defaultFeatures.length} default/suggested features and ${customFeatures.length} custom features`,
|
||||||
templateInfo: templateCheck.rows[0]
|
templateInfo: templateCheck.rows[0]
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default function BusinessQuestionsScreen() {
|
|||||||
console.log('🚀 Generating comprehensive business questions for integrated system:', selectedFeatures);
|
console.log('🚀 Generating comprehensive business questions for integrated system:', selectedFeatures);
|
||||||
|
|
||||||
// Call the new comprehensive endpoint
|
// Call the new comprehensive endpoint
|
||||||
const response = await fetch('http://localhost:8001/api/v1/generate-comprehensive-business-questions', {
|
const response = await fetch('http://localhost:8000/api/v1/generate-comprehensive-business-questions', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user