Files
the_order/services/identity/src/metrics.ts
defiQUG 92cc41d26d Add Legal Office seal and complete Azure CDN deployment
- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll)
- Create legal-office-manifest-template.json for Legal Office credentials
- Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation
- Complete Azure CDN infrastructure deployment:
  - Resource group, storage account, and container created
  - 17 PNG seal files uploaded to Azure Blob Storage
  - All manifest templates updated with Azure URLs
  - Configuration files generated (azure-cdn-config.env)
- Add comprehensive Azure CDN setup scripts and documentation
- Fix manifest URL generation to prevent double slashes
- Verify all seals accessible via HTTPS
2025-11-12 22:03:42 -08:00

251 lines
7.0 KiB
TypeScript

/**
* Credential issuance metrics and dashboard
* Real-time metrics: issued per day/week/month, success/failure rates, average issuance time, credential types distribution
*/
import { getAuditStatistics, searchAuditLogs } from '@the-order/database';
// import { getPool } from '@the-order/database'; // Not used in this file
import { query } from '@the-order/database';
export interface CredentialMetrics {
// Time-based metrics
issuedToday: number;
issuedThisWeek: number;
issuedThisMonth: number;
issuedThisYear: number;
// Success/failure rates
successRate: number; // percentage
failureRate: number; // percentage
totalIssuances: number;
totalFailures: number;
// Performance metrics
averageIssuanceTime: number; // milliseconds
p50IssuanceTime: number; // milliseconds
p95IssuanceTime: number; // milliseconds
p99IssuanceTime: number; // milliseconds
// Credential type distribution
byCredentialType: Record<string, number>;
byAction: Record<string, number>;
// Recent activity
recentIssuances: Array<{
credentialId: string;
credentialType: string[];
issuedAt: Date;
subjectDid: string;
}>;
}
/**
* Get credential issuance metrics
*/
export async function getCredentialMetrics(
startDate?: Date,
endDate?: Date
): Promise<CredentialMetrics> {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
const monthAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
const yearAgo = new Date(today.getFullYear(), 0, 1);
// Get statistics
const stats = await getAuditStatistics(startDate || yearAgo, endDate || now);
// Get time-based counts
const issuedToday = await getIssuanceCount(today, now);
const issuedThisWeek = await getIssuanceCount(weekAgo, now);
const issuedThisMonth = await getIssuanceCount(monthAgo, now);
const issuedThisYear = await getIssuanceCount(yearAgo, now);
// Get performance metrics (would need to track issuance time in audit log)
const performanceMetrics = await getPerformanceMetrics(startDate || yearAgo, endDate || now);
// Get recent issuances
const recentIssuancesResult = await searchAuditLogs(
{ action: 'issued' },
1,
10
);
const recentIssuances = recentIssuancesResult.logs.map((log) => ({
credentialId: log.credential_id,
credentialType: log.credential_type,
issuedAt: log.performed_at,
subjectDid: log.subject_did,
}));
// Calculate success/failure rates
const totalIssuances = stats.totalIssuances;
const totalFailures = 0; // Would need to track failures separately
const successRate = totalIssuances > 0 ? ((totalIssuances - totalFailures) / totalIssuances) * 100 : 100;
const failureRate = totalIssuances > 0 ? (totalFailures / totalIssuances) * 100 : 0;
return {
issuedToday,
issuedThisWeek,
issuedThisMonth,
issuedThisYear,
successRate,
failureRate,
totalIssuances,
totalFailures,
averageIssuanceTime: performanceMetrics.average,
p50IssuanceTime: performanceMetrics.p50,
p95IssuanceTime: performanceMetrics.p95,
p99IssuanceTime: performanceMetrics.p99,
byCredentialType: stats.byCredentialType,
byAction: stats.byAction,
recentIssuances,
};
}
/**
* Get issuance count for time period
*/
async function getIssuanceCount(startDate: Date, endDate: Date): Promise<number> {
const result = await query<{ count: string }>(
`SELECT COUNT(*) as count
FROM credential_issuance_audit
WHERE action = 'issued'
AND performed_at >= $1
AND performed_at <= $2`,
[startDate, endDate]
);
return parseInt(result.rows[0]?.count || '0', 10);
}
/**
* Get performance metrics
* Note: This requires tracking issuance time in the audit log metadata
*/
async function getPerformanceMetrics(
_startDate: Date,
_endDate: Date
): Promise<{
average: number;
p50: number;
p95: number;
p99: number;
}> {
// In production, this would query metadata for issuance times
// For now, return placeholder values
return {
average: 500, // milliseconds
p50: 400,
p95: 1000,
p99: 2000,
};
}
/**
* Get metrics dashboard data
*/
export async function getMetricsDashboard(): Promise<{
summary: CredentialMetrics;
trends: {
daily: Array<{ date: string; count: number }>;
weekly: Array<{ week: string; count: number }>;
monthly: Array<{ month: string; count: number }>;
};
topCredentialTypes: Array<{ type: string; count: number; percentage: number }>;
}> {
const summary = await getCredentialMetrics();
// Get daily trends (last 30 days)
const dailyTrends = await getDailyTrends(30);
const weeklyTrends = await getWeeklyTrends(12);
const monthlyTrends = await getMonthlyTrends(12);
// Calculate top credential types
const total = Object.values(summary.byCredentialType).reduce((sum, count) => sum + count, 0);
const topCredentialTypes = Object.entries(summary.byCredentialType)
.map(([type, count]) => ({
type,
count,
percentage: total > 0 ? (count / total) * 100 : 0,
}))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
return {
summary,
trends: {
daily: dailyTrends,
weekly: weeklyTrends,
monthly: monthlyTrends,
},
topCredentialTypes,
};
}
/**
* Get daily trends
*/
async function getDailyTrends(days: number): Promise<Array<{ date: string; count: number }>> {
const result = await query<{ date: string; count: string }>(
`SELECT
DATE(performed_at) as date,
COUNT(*) as count
FROM credential_issuance_audit
WHERE action = 'issued'
AND performed_at >= NOW() - INTERVAL '1 day' * $1
GROUP BY DATE(performed_at)
ORDER BY date DESC`,
[days]
);
return result.rows.map((row) => ({
date: row.date,
count: parseInt(row.count, 10),
}));
}
/**
* Get weekly trends
*/
async function getWeeklyTrends(weeks: number): Promise<Array<{ week: string; count: number }>> {
const result = await query<{ week: string; count: string }>(
`SELECT
DATE_TRUNC('week', performed_at) as week,
COUNT(*) as count
FROM credential_issuance_audit
WHERE action = 'issued'
AND performed_at >= NOW() - INTERVAL '1 week' * $1
GROUP BY DATE_TRUNC('week', performed_at)
ORDER BY week DESC`,
[weeks]
);
return result.rows.map((row) => ({
week: row.week,
count: parseInt(row.count, 10),
}));
}
/**
* Get monthly trends
*/
async function getMonthlyTrends(months: number): Promise<Array<{ month: string; count: number }>> {
const result = await query<{ month: string; count: string }>(
`SELECT
DATE_TRUNC('month', performed_at) as month,
COUNT(*) as count
FROM credential_issuance_audit
WHERE action = 'issued'
AND performed_at >= NOW() - INTERVAL '1 month' * $1
GROUP BY DATE_TRUNC('month', performed_at)
ORDER BY month DESC`,
[months]
);
return result.rows.map((row) => ({
month: row.month,
count: parseInt(row.count, 10),
}));
}