@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
330 lines (289 loc) • 10.5 kB
text/typescript
import { Application, Request, Response } from 'express';
import { ensureDatabaseReady } from '../../storage/sqlite-manager.js';
export function setupApprovalsAPI(app: Application): void {
// Get all approval requests with filtering
app.get('/api/approvals', async (req: Request, res: Response) => {
try {
const {
status,
urgency,
action,
limit = 50,
offset = 0
} = req.query;
let sql = 'SELECT * FROM approval_requests WHERE 1=1';
const params: any[] = [];
if (status) {
sql += ' AND status = ?';
params.push(status);
}
if (urgency) {
sql += ' AND urgency = ?';
params.push(urgency);
}
if (action) {
sql += ' AND action LIKE ?';
params.push(`%${action}%`);
}
sql += ' ORDER BY requested_at DESC LIMIT ? OFFSET ?';
params.push(Number(limit), Number(offset));
const db = await ensureDatabaseReady();
const result = await db.query(sql, params);
if (!result.success) {
throw new Error(result.error || 'Database query failed');
}
const approvals = (result.data || []).map((approval: any) => {
let context = {};
try {
context = approval.context ? JSON.parse(approval.context) : {};
} catch (e) {
console.warn('Failed to parse approval context:', approval.id);
}
return {
id: approval.id,
action: approval.action,
context,
description: approval.description,
urgency: approval.urgency,
status: approval.status,
requestedBy: approval.requested_by,
requestedAt: approval.requested_at ? new Date(Number(approval.requested_at)).toISOString() : new Date().toISOString(),
resolvedBy: approval.resolved_by,
resolvedAt: approval.resolved_at ? new Date(Number(approval.resolved_at)).toISOString() : null,
timeoutAt: approval.timeout_at ? new Date(Number(approval.timeout_at)).toISOString() : null,
responseData: approval.response_data ? JSON.parse(approval.response_data) : null,
notes: approval.notes
};
});
res.json({
success: true,
data: { approvals },
count: approvals.length,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📋 Error fetching approvals:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch approval requests',
message: (error as Error).message
});
}
});
// Get approval statistics
app.get('/api/approvals/stats', async (req: Request, res: Response) => {
try {
const { timeframe = '7d' } = req.query;
// Calculate time threshold
const now = Date.now();
let timeThreshold = now;
switch (timeframe) {
case '24h':
timeThreshold = now - (24 * 60 * 60 * 1000);
break;
case '7d':
timeThreshold = now - (7 * 24 * 60 * 60 * 1000);
break;
case '30d':
timeThreshold = now - (30 * 24 * 60 * 60 * 1000);
break;
default:
timeThreshold = now - (7 * 24 * 60 * 60 * 1000);
}
const db = await ensureDatabaseReady();
// Get total counts
const totalResult = await db.get(
'SELECT COUNT(*) as total FROM approval_requests WHERE requested_at > ?',
[timeThreshold]
);
const pendingResult = await db.get(
'SELECT COUNT(*) as pending FROM approval_requests WHERE status = ? AND requested_at > ?',
['pending', timeThreshold]
);
const approvedResult = await db.get(
'SELECT COUNT(*) as approved FROM approval_requests WHERE status = ? AND requested_at > ?',
['approved', timeThreshold]
);
const rejectedResult = await db.get(
'SELECT COUNT(*) as rejected FROM approval_requests WHERE status = ? AND requested_at > ?',
['rejected', timeThreshold]
);
const expiredResult = await db.get(
'SELECT COUNT(*) as expired FROM approval_requests WHERE status = ? AND requested_at > ?',
['expired', timeThreshold]
);
// Get urgency breakdown
const urgencyResult = await db.query(
'SELECT urgency, COUNT(*) as count FROM approval_requests WHERE requested_at > ? GROUP BY urgency',
[timeThreshold]
);
const urgencyBreakdown: Record<string, number> = {};
if (urgencyResult.success && urgencyResult.data) {
urgencyResult.data.forEach((row: any) => {
urgencyBreakdown[row.urgency] = row.count;
});
}
const stats = {
total: totalResult.data?.total || 0,
pending: pendingResult.data?.pending || 0,
approved: approvedResult.data?.approved || 0,
rejected: rejectedResult.data?.rejected || 0,
expired: expiredResult.data?.expired || 0,
urgencyBreakdown,
timeframe
};
res.json({
success: true,
data: stats,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📋 Error fetching approval stats:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch approval statistics',
message: (error as Error).message
});
}
});
// Health check for approvals service
app.get('/api/approvals/health', async (req: Request, res: Response) => {
try {
// Test database connection
const db = await ensureDatabaseReady();
const testResult = await db.get('SELECT COUNT(*) as count FROM approval_requests LIMIT 1');
if (!testResult.success) {
throw new Error('Database connection failed');
}
res.json({
success: true,
status: 'healthy',
data: {
databaseConnected: true,
approvalSystemActive: true
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📋 Approvals health check failed:', error);
res.status(503).json({
success: false,
status: 'unhealthy',
error: 'Approval system is not functioning properly',
message: (error as Error).message,
timestamp: new Date().toISOString()
});
}
});
// Get specific approval request
app.get('/api/approvals/:id', async (req: Request, res: Response) => {
try {
const { id } = req.params;
const db = await ensureDatabaseReady();
const result = await db.get(
'SELECT * FROM approval_requests WHERE id = ?',
[id]
);
if (!result.success) {
throw new Error(result.error || 'Database query failed');
}
if (!result.data) {
return res.status(404).json({
success: false,
error: 'Approval request not found',
approvalId: id
});
}
const approval = result.data;
let context = {};
try {
context = approval.context ? JSON.parse(approval.context) : {};
} catch (e) {
console.warn('Failed to parse approval context:', approval.id);
}
const approvalData = {
id: approval.id,
action: approval.action,
context,
description: approval.description,
urgency: approval.urgency,
status: approval.status,
requestedBy: approval.requested_by,
requestedAt: approval.requested_at ? new Date(Number(approval.requested_at)).toISOString() : new Date().toISOString(),
resolvedBy: approval.resolved_by,
resolvedAt: approval.resolved_at ? new Date(Number(approval.resolved_at)).toISOString() : null,
timeoutAt: approval.timeout_at ? new Date(Number(approval.timeout_at)).toISOString() : null,
responseData: approval.response_data ? JSON.parse(approval.response_data) : null,
notes: approval.notes
};
res.json({
success: true,
data: approvalData,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error(`📋 Error fetching approval ${req.params.id}:`, error);
res.status(500).json({
success: false,
error: 'Failed to fetch approval request',
message: (error as Error).message
});
}
});
// Approve or reject an approval request
app.post('/api/approvals/:id/respond', async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { decision, notes, responseData, resolvedBy } = req.body;
if (!decision || !['approved', 'rejected'].includes(decision)) {
return res.status(400).json({
success: false,
error: 'Invalid decision. Must be "approved" or "rejected"'
});
}
const now = Date.now();
const db = await ensureDatabaseReady();
const result = await db.run(
`UPDATE approval_requests
SET status = ?, resolved_by = ?, resolved_at = ?, notes = ?, response_data = ?
WHERE id = ? AND status = 'pending'`,
[
decision,
resolvedBy || 'dashboard-user',
now,
notes || null,
responseData ? JSON.stringify(responseData) : null,
id
]
);
if (!result.success) {
throw new Error(result.error || 'Database update failed');
}
if (result.data?.changes === 0) {
return res.status(404).json({
success: false,
error: 'Approval request not found or already resolved'
});
}
res.json({
success: true,
message: `Approval request ${decision}`,
data: {
approvalId: id,
decision,
resolvedBy: resolvedBy || 'dashboard-user',
resolvedAt: new Date(now).toISOString(),
notes
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error(`📋 Error responding to approval ${req.params.id}:`, error);
res.status(500).json({
success: false,
error: 'Failed to respond to approval request',
message: (error as Error).message
});
}
});
}