feat: Refactor Settings and Viewer routes to extend BaseRouteHandler for improved error handling
- Introduced BaseRouteHandler class to centralize error handling and response management. - Updated SettingsRoutes to use wrapHandler for automatic error logging and response. - Refactored ViewerRoutes to extend BaseRouteHandler and utilize wrapHandler for health check and UI serving. - Enhanced error handling in SettingsRoutes and ViewerRoutes for better maintainability and readability.
This commit is contained in:
@@ -11,14 +11,14 @@ import { readFileSync, statSync, existsSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { getPackageRoot } from '../../../../shared/paths.js';
|
||||
import { getWorkerPort } from '../../../../shared/worker-utils.js';
|
||||
import { logger } from '../../../../utils/logger.js';
|
||||
import { PaginationHelper } from '../../PaginationHelper.js';
|
||||
import { DatabaseManager } from '../../DatabaseManager.js';
|
||||
import { SessionManager } from '../../SessionManager.js';
|
||||
import { SSEBroadcaster } from '../../SSEBroadcaster.js';
|
||||
import type { WorkerService } from '../../../worker-service.js';
|
||||
import { BaseRouteHandler } from '../BaseRouteHandler.js';
|
||||
|
||||
export class DataRoutes {
|
||||
export class DataRoutes extends BaseRouteHandler {
|
||||
constructor(
|
||||
private paginationHelper: PaginationHelper,
|
||||
private dbManager: DatabaseManager,
|
||||
@@ -26,7 +26,9 @@ export class DataRoutes {
|
||||
private sseBroadcaster: SSEBroadcaster,
|
||||
private workerService: WorkerService,
|
||||
private startTime: number
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
setupRoutes(app: express.Application): void {
|
||||
// Pagination endpoints
|
||||
@@ -51,233 +53,178 @@ export class DataRoutes {
|
||||
/**
|
||||
* Get paginated observations
|
||||
*/
|
||||
private handleGetObservations(req: Request, res: Response): void {
|
||||
try {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getObservations(offset, limit, project);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get observations failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
private handleGetObservations = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getObservations(offset, limit, project);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get paginated summaries
|
||||
*/
|
||||
private handleGetSummaries(req: Request, res: Response): void {
|
||||
try {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getSummaries(offset, limit, project);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get summaries failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
private handleGetSummaries = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getSummaries(offset, limit, project);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get paginated user prompts
|
||||
*/
|
||||
private handleGetPrompts(req: Request, res: Response): void {
|
||||
try {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getPrompts(offset, limit, project);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get prompts failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
private handleGetPrompts = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getPrompts(offset, limit, project);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get observation by ID
|
||||
* GET /api/observation/:id
|
||||
*/
|
||||
private handleGetObservationById(req: Request, res: Response): void {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ error: 'Invalid observation ID' });
|
||||
return;
|
||||
}
|
||||
private handleGetObservationById = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const id = this.parseIntParam(req, res, 'id');
|
||||
if (id === null) return;
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const observation = store.getObservationById(id);
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const observation = store.getObservationById(id);
|
||||
|
||||
if (!observation) {
|
||||
res.status(404).json({ error: `Observation #${id} not found` });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(observation);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get observation by ID failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (!observation) {
|
||||
this.notFound(res, `Observation #${id} not found`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.json(observation);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get session by ID
|
||||
* GET /api/session/:id
|
||||
*/
|
||||
private handleGetSessionById(req: Request, res: Response): void {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ error: 'Invalid session ID' });
|
||||
return;
|
||||
}
|
||||
private handleGetSessionById = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const id = this.parseIntParam(req, res, 'id');
|
||||
if (id === null) return;
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const sessions = store.getSessionSummariesByIds([id]);
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const sessions = store.getSessionSummariesByIds([id]);
|
||||
|
||||
if (sessions.length === 0) {
|
||||
res.status(404).json({ error: `Session #${id} not found` });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(sessions[0]);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get session by ID failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (sessions.length === 0) {
|
||||
this.notFound(res, `Session #${id} not found`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.json(sessions[0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get user prompt by ID
|
||||
* GET /api/prompt/:id
|
||||
*/
|
||||
private handleGetPromptById(req: Request, res: Response): void {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
if (isNaN(id)) {
|
||||
res.status(400).json({ error: 'Invalid prompt ID' });
|
||||
return;
|
||||
}
|
||||
private handleGetPromptById = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const id = this.parseIntParam(req, res, 'id');
|
||||
if (id === null) return;
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const prompts = store.getUserPromptsByIds([id]);
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const prompts = store.getUserPromptsByIds([id]);
|
||||
|
||||
if (prompts.length === 0) {
|
||||
res.status(404).json({ error: `Prompt #${id} not found` });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(prompts[0]);
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get prompt by ID failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (prompts.length === 0) {
|
||||
this.notFound(res, `Prompt #${id} not found`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.json(prompts[0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get database statistics (with worker metadata)
|
||||
*/
|
||||
private handleGetStats(req: Request, res: Response): void {
|
||||
try {
|
||||
const db = this.dbManager.getSessionStore().db;
|
||||
private handleGetStats = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const db = this.dbManager.getSessionStore().db;
|
||||
|
||||
// Read version from package.json
|
||||
const packageRoot = getPackageRoot();
|
||||
const packageJsonPath = path.join(packageRoot, 'package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
const version = packageJson.version;
|
||||
// Read version from package.json
|
||||
const packageRoot = getPackageRoot();
|
||||
const packageJsonPath = path.join(packageRoot, 'package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
const version = packageJson.version;
|
||||
|
||||
// Get database stats
|
||||
const totalObservations = db.prepare('SELECT COUNT(*) as count FROM observations').get() as { count: number };
|
||||
const totalSessions = db.prepare('SELECT COUNT(*) as count FROM sdk_sessions').get() as { count: number };
|
||||
const totalSummaries = db.prepare('SELECT COUNT(*) as count FROM session_summaries').get() as { count: number };
|
||||
// Get database stats
|
||||
const totalObservations = db.prepare('SELECT COUNT(*) as count FROM observations').get() as { count: number };
|
||||
const totalSessions = db.prepare('SELECT COUNT(*) as count FROM sdk_sessions').get() as { count: number };
|
||||
const totalSummaries = db.prepare('SELECT COUNT(*) as count FROM session_summaries').get() as { count: number };
|
||||
|
||||
// Get database file size and path
|
||||
const dbPath = path.join(homedir(), '.claude-mem', 'claude-mem.db');
|
||||
let dbSize = 0;
|
||||
if (existsSync(dbPath)) {
|
||||
dbSize = statSync(dbPath).size;
|
||||
}
|
||||
|
||||
// Worker metadata
|
||||
const uptime = Math.floor((Date.now() - this.startTime) / 1000);
|
||||
const activeSessions = this.sessionManager.getActiveSessionCount();
|
||||
const sseClients = this.sseBroadcaster.getClientCount();
|
||||
|
||||
res.json({
|
||||
worker: {
|
||||
version,
|
||||
uptime,
|
||||
activeSessions,
|
||||
sseClients,
|
||||
port: getWorkerPort()
|
||||
},
|
||||
database: {
|
||||
path: dbPath,
|
||||
size: dbSize,
|
||||
observations: totalObservations.count,
|
||||
sessions: totalSessions.count,
|
||||
summaries: totalSummaries.count
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get stats failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
// Get database file size and path
|
||||
const dbPath = path.join(homedir(), '.claude-mem', 'claude-mem.db');
|
||||
let dbSize = 0;
|
||||
if (existsSync(dbPath)) {
|
||||
dbSize = statSync(dbPath).size;
|
||||
}
|
||||
}
|
||||
|
||||
// Worker metadata
|
||||
const uptime = Math.floor((Date.now() - this.startTime) / 1000);
|
||||
const activeSessions = this.sessionManager.getActiveSessionCount();
|
||||
const sseClients = this.sseBroadcaster.getClientCount();
|
||||
|
||||
res.json({
|
||||
worker: {
|
||||
version,
|
||||
uptime,
|
||||
activeSessions,
|
||||
sseClients,
|
||||
port: getWorkerPort()
|
||||
},
|
||||
database: {
|
||||
path: dbPath,
|
||||
size: dbSize,
|
||||
observations: totalObservations.count,
|
||||
sessions: totalSessions.count,
|
||||
summaries: totalSummaries.count
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get list of distinct projects from observations
|
||||
* GET /api/projects
|
||||
*/
|
||||
private handleGetProjects(req: Request, res: Response): void {
|
||||
try {
|
||||
const db = this.dbManager.getSessionStore().db;
|
||||
private handleGetProjects = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const db = this.dbManager.getSessionStore().db;
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT DISTINCT project
|
||||
FROM observations
|
||||
WHERE project IS NOT NULL
|
||||
GROUP BY project
|
||||
ORDER BY MAX(created_at_epoch) DESC
|
||||
`).all() as Array<{ project: string }>;
|
||||
const rows = db.prepare(`
|
||||
SELECT DISTINCT project
|
||||
FROM observations
|
||||
WHERE project IS NOT NULL
|
||||
GROUP BY project
|
||||
ORDER BY MAX(created_at_epoch) DESC
|
||||
`).all() as Array<{ project: string }>;
|
||||
|
||||
const projects = rows.map(row => row.project);
|
||||
const projects = rows.map(row => row.project);
|
||||
|
||||
res.json({ projects });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Get projects failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
res.json({ projects });
|
||||
});
|
||||
|
||||
/**
|
||||
* Get current processing status
|
||||
* GET /api/processing-status
|
||||
*/
|
||||
private handleGetProcessingStatus(req: Request, res: Response): void {
|
||||
private handleGetProcessingStatus = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const isProcessing = this.sessionManager.isAnySessionProcessing();
|
||||
const queueDepth = this.sessionManager.getTotalActiveWork(); // Includes queued + actively processing
|
||||
res.json({ isProcessing, queueDepth });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Set processing status (called by hooks)
|
||||
* NOTE: This now broadcasts computed status based on active processing (ignores input)
|
||||
*/
|
||||
private handleSetProcessing(req: Request, res: Response): void {
|
||||
try {
|
||||
// Broadcast current computed status (ignores manual input)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
private handleSetProcessing = this.wrapHandler((req: Request, res: Response): void => {
|
||||
// Broadcast current computed status (ignores manual input)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
const isProcessing = this.sessionManager.isAnySessionProcessing();
|
||||
const queueDepth = this.sessionManager.getTotalQueueDepth();
|
||||
const activeSessions = this.sessionManager.getActiveSessionCount();
|
||||
logger.debug('WORKER', 'Processing status broadcast', { isProcessing, queueDepth, activeSessions });
|
||||
const isProcessing = this.sessionManager.isAnySessionProcessing();
|
||||
const queueDepth = this.sessionManager.getTotalQueueDepth();
|
||||
const activeSessions = this.sessionManager.getActiveSessionCount();
|
||||
|
||||
res.json({ status: 'ok', isProcessing });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Failed to broadcast processing status', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
res.json({ status: 'ok', isProcessing });
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse pagination parameters from request query
|
||||
|
||||
Reference in New Issue
Block a user