mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
feat: enhance ideation routes with event handling and new suggestion feature
- Updated the ideation routes to include an EventEmitter for better event management. - Added a new endpoint to handle adding suggestions to the board, ensuring consistent category mapping. - Modified existing routes to emit events for idea creation, update, and deletion, improving frontend notifications. - Refactored the convert and create idea handlers to utilize the new event system. - Removed static guided prompts data in favor of dynamic fetching from the backend API.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import type { IdeationService } from '../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../services/feature-loader.js';
|
||||
@@ -19,10 +20,12 @@ import { createIdeasUpdateHandler } from './routes/ideas-update.js';
|
||||
import { createIdeasDeleteHandler } from './routes/ideas-delete.js';
|
||||
import { createAnalyzeHandler, createGetAnalysisHandler } from './routes/analyze.js';
|
||||
import { createConvertHandler } from './routes/convert.js';
|
||||
import { createAddSuggestionHandler } from './routes/add-suggestion.js';
|
||||
import { createPromptsHandler, createPromptsByCategoryHandler } from './routes/prompts.js';
|
||||
import { createSuggestionsGenerateHandler } from './routes/suggestions-generate.js';
|
||||
|
||||
export function createIdeationRoutes(
|
||||
events: EventEmitter,
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
): Router {
|
||||
@@ -35,7 +38,7 @@ export function createIdeationRoutes(
|
||||
createSessionStartHandler(ideationService)
|
||||
);
|
||||
router.post('/session/message', createSessionMessageHandler(ideationService));
|
||||
router.post('/session/stop', createSessionStopHandler(ideationService));
|
||||
router.post('/session/stop', createSessionStopHandler(events, ideationService));
|
||||
router.post(
|
||||
'/session/get',
|
||||
validatePathParams('projectPath'),
|
||||
@@ -51,7 +54,7 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/ideas/create',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasCreateHandler(ideationService)
|
||||
createIdeasCreateHandler(events, ideationService)
|
||||
);
|
||||
router.post(
|
||||
'/ideas/get',
|
||||
@@ -61,12 +64,12 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/ideas/update',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasUpdateHandler(ideationService)
|
||||
createIdeasUpdateHandler(events, ideationService)
|
||||
);
|
||||
router.post(
|
||||
'/ideas/delete',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasDeleteHandler(ideationService)
|
||||
createIdeasDeleteHandler(events, ideationService)
|
||||
);
|
||||
|
||||
// Project analysis
|
||||
@@ -81,7 +84,14 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/convert',
|
||||
validatePathParams('projectPath'),
|
||||
createConvertHandler(ideationService, featureLoader)
|
||||
createConvertHandler(events, ideationService, featureLoader)
|
||||
);
|
||||
|
||||
// Add suggestion to board as a feature
|
||||
router.post(
|
||||
'/add-suggestion',
|
||||
validatePathParams('projectPath'),
|
||||
createAddSuggestionHandler(ideationService, featureLoader)
|
||||
);
|
||||
|
||||
// Guided prompts (no validation needed - static data)
|
||||
|
||||
70
apps/server/src/routes/ideation/routes/add-suggestion.ts
Normal file
70
apps/server/src/routes/ideation/routes/add-suggestion.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* POST /add-suggestion - Add an analysis suggestion to the board as a feature
|
||||
*
|
||||
* This endpoint converts an AnalysisSuggestion to a Feature using the
|
||||
* IdeationService's mapIdeaCategoryToFeatureCategory for consistent category mapping.
|
||||
* This ensures a single source of truth for the conversion logic.
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { AnalysisSuggestion } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createAddSuggestionHandler(
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, suggestion } = req.body as {
|
||||
projectPath: string;
|
||||
suggestion: AnalysisSuggestion;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion) {
|
||||
res.status(400).json({ success: false, error: 'suggestion is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion.title) {
|
||||
res.status(400).json({ success: false, error: 'suggestion.title is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion.category) {
|
||||
res.status(400).json({ success: false, error: 'suggestion.category is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Build description with rationale if provided
|
||||
const description = suggestion.rationale
|
||||
? `${suggestion.description}\n\n**Rationale:** ${suggestion.rationale}`
|
||||
: suggestion.description;
|
||||
|
||||
// Use the service's category mapping for consistency
|
||||
const featureCategory = ideationService.mapSuggestionCategoryToFeatureCategory(
|
||||
suggestion.category
|
||||
);
|
||||
|
||||
// Create the feature
|
||||
const feature = await featureLoader.create(projectPath, {
|
||||
title: suggestion.title,
|
||||
description,
|
||||
category: featureCategory,
|
||||
status: 'backlog',
|
||||
});
|
||||
|
||||
res.json({ success: true, featureId: feature.id });
|
||||
} catch (error) {
|
||||
logError(error, 'Add suggestion to board failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { ConvertToFeatureOptions } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createConvertHandler(
|
||||
events: EventEmitter,
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
) {
|
||||
@@ -49,8 +51,22 @@ export function createConvertHandler(
|
||||
// Delete the idea unless keepIdea is explicitly true
|
||||
if (!keepIdea) {
|
||||
await ideationService.deleteIdea(projectPath, ideaId);
|
||||
|
||||
// Emit idea deleted event
|
||||
events.emit('ideation:idea-deleted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit idea converted event to notify frontend
|
||||
events.emit('ideation:idea-converted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
featureId: feature.id,
|
||||
keepIdea: !!keepIdea,
|
||||
});
|
||||
|
||||
// Return featureId as expected by the frontend API interface
|
||||
res.json({ success: true, featureId: feature.id });
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { CreateIdeaInput } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasCreateHandler(ideationService: IdeationService) {
|
||||
export function createIdeasCreateHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, idea } = req.body as {
|
||||
@@ -34,6 +35,13 @@ export function createIdeasCreateHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
const created = await ideationService.createIdea(projectPath, idea);
|
||||
|
||||
// Emit idea created event for frontend notification
|
||||
events.emit('ideation:idea-created', {
|
||||
projectPath,
|
||||
idea: created,
|
||||
});
|
||||
|
||||
res.json({ success: true, idea: created });
|
||||
} catch (error) {
|
||||
logError(error, 'Create idea failed');
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasDeleteHandler(ideationService: IdeationService) {
|
||||
export function createIdeasDeleteHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, ideaId } = req.body as {
|
||||
@@ -25,6 +26,13 @@ export function createIdeasDeleteHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
await ideationService.deleteIdea(projectPath, ideaId);
|
||||
|
||||
// Emit idea deleted event for frontend notification
|
||||
events.emit('ideation:idea-deleted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, 'Delete idea failed');
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { UpdateIdeaInput } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasUpdateHandler(ideationService: IdeationService) {
|
||||
export function createIdeasUpdateHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, ideaId, updates } = req.body as {
|
||||
@@ -37,6 +38,13 @@ export function createIdeasUpdateHandler(ideationService: IdeationService) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit idea updated event for frontend notification
|
||||
events.emit('ideation:idea-updated', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
idea,
|
||||
});
|
||||
|
||||
res.json({ success: true, idea });
|
||||
} catch (error) {
|
||||
logError(error, 'Update idea failed');
|
||||
|
||||
@@ -26,7 +26,7 @@ export function createPromptsByCategoryHandler(ideationService: IdeationService)
|
||||
try {
|
||||
const { category } = req.params as { category: string };
|
||||
|
||||
const validCategories: IdeaCategory[] = ['feature', 'ux-ui', 'dx', 'growth', 'technical'];
|
||||
const validCategories = ideationService.getPromptCategories().map((c) => c.id);
|
||||
if (!validCategories.includes(category as IdeaCategory)) {
|
||||
res.status(400).json({ success: false, error: 'Invalid category' });
|
||||
return;
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createSessionStopHandler(ideationService: IdeationService) {
|
||||
export function createSessionStopHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { sessionId } = req.body as { sessionId: string };
|
||||
const { sessionId, projectPath } = req.body as {
|
||||
sessionId: string;
|
||||
projectPath?: string;
|
||||
};
|
||||
|
||||
if (!sessionId) {
|
||||
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||
@@ -17,6 +21,15 @@ export function createSessionStopHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
await ideationService.stopSession(sessionId);
|
||||
|
||||
// Emit session stopped event for frontend notification
|
||||
// Note: The service also emits 'ideation:session-ended' internally,
|
||||
// but we emit here as well for route-level consistency with other routes
|
||||
events.emit('ideation:session-ended', {
|
||||
sessionId,
|
||||
projectPath,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, 'Stop session failed');
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
const logger = createLogger('ideation:suggestions-generate');
|
||||
|
||||
@@ -45,10 +46,10 @@ export function createSuggestionsGenerateHandler(ideationService: IdeationServic
|
||||
suggestions,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate suggestions:', error);
|
||||
logError(error, 'Failed to generate suggestions');
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user