a cache for slack profile pictures and emojis

feat: add fancy health check

dunkirk.sh 9e439f46 d8035dc6

verified
Changed files
+141 -3
src
handlers
routes
+80
src/cache.ts
···
}
/**
+
* Detailed health check with component status
+
* @returns Object with detailed health information
+
*/
+
async detailedHealthCheck(): Promise<{
+
status: "healthy" | "degraded" | "unhealthy";
+
checks: {
+
database: { status: boolean; latency?: number };
+
slackApi: { status: boolean; error?: string };
+
queueDepth: number;
+
memoryUsage: {
+
heapUsed: number;
+
heapTotal: number;
+
percentage: number;
+
};
+
};
+
uptime: number;
+
}> {
+
const checks = {
+
database: { status: false, latency: 0 },
+
slackApi: { status: false },
+
queueDepth: this.userUpdateQueue.size,
+
memoryUsage: {
+
heapUsed: 0,
+
heapTotal: 0,
+
percentage: 0,
+
},
+
};
+
+
// Check database
+
try {
+
const start = Date.now();
+
this.db.query("SELECT 1").get();
+
checks.database = { status: true, latency: Date.now() - start };
+
} catch (error) {
+
console.error("Database health check failed:", error);
+
}
+
+
// Check Slack API if wrapper is available
+
if (this.slackWrapper) {
+
try {
+
await this.slackWrapper.getUserInfo("U062UG485EE"); // Use a known test user
+
checks.slackApi = { status: true };
+
} catch (error) {
+
checks.slackApi = {
+
status: false,
+
error: error instanceof Error ? error.message : "Unknown error",
+
};
+
}
+
} else {
+
checks.slackApi = { status: true }; // No wrapper means not critical
+
}
+
+
// Check memory usage
+
const memUsage = process.memoryUsage();
+
checks.memoryUsage = {
+
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
+
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
+
percentage: Math.round(
+
(memUsage.heapUsed / memUsage.heapTotal) * 100,
+
),
+
};
+
+
// Determine overall status
+
let status: "healthy" | "degraded" | "unhealthy" = "healthy";
+
if (!checks.database.status) {
+
status = "unhealthy";
+
} else if (!checks.slackApi.status || checks.queueDepth > 100) {
+
status = "degraded";
+
} else if (checks.memoryUsage.percentage > 90) {
+
status = "degraded";
+
}
+
+
return {
+
status,
+
checks,
+
uptime: process.uptime(),
+
};
+
}
+
+
/**
* Sets the Slack wrapper for user updates
* @param slackWrapper SlackUserProvider instance for API calls
*/
+11 -1
src/handlers/index.ts
···
}
export const handleHealthCheck: RouteHandlerWithAnalytics = async (
-
_request,
+
request,
recordAnalytics,
) => {
+
const url = new URL(request.url);
+
const detailed = url.searchParams.get("detailed") === "true";
+
+
if (detailed) {
+
const health = await cache.detailedHealthCheck();
+
const statusCode = health.status === "unhealthy" ? 503 : health.status === "degraded" ? 200 : 200;
+
await recordAnalytics(statusCode);
+
return Response.json(health, { status: statusCode });
+
}
+
const isHealthy = await cache.healthCheck();
if (isHealthy) {
await recordAnalytics(200);
+50 -2
src/routes/api-routes.ts
···
withAnalytics("/health", "GET", handlers.handleHealthCheck),
{
summary: "Health check",
-
description: "Check if the service is healthy and operational",
+
description:
+
"Check if the service is healthy and operational. Add ?detailed=true for comprehensive health information including Slack API status, queue depth, and memory usage.",
tags: ["Health"],
+
parameters: {
+
query: [
+
queryParam(
+
"detailed",
+
"boolean",
+
"Return detailed health check information",
+
false,
+
false,
+
),
+
],
+
},
responses: Object.fromEntries([
apiResponse(200, "Service is healthy", {
type: "object",
properties: {
-
status: { type: "string", example: "healthy" },
+
status: {
+
type: "string",
+
example: "healthy",
+
enum: ["healthy", "degraded", "unhealthy"],
+
},
cache: { type: "boolean", example: true },
uptime: { type: "number", example: 123456 },
+
checks: {
+
type: "object",
+
description: "Detailed checks (only with ?detailed=true)",
+
properties: {
+
database: {
+
type: "object",
+
properties: {
+
status: { type: "boolean" },
+
latency: { type: "number", description: "ms" },
+
},
+
},
+
slackApi: {
+
type: "object",
+
properties: {
+
status: { type: "boolean" },
+
error: { type: "string" },
+
},
+
},
+
queueDepth: {
+
type: "number",
+
description: "Number of users queued for update",
+
},
+
memoryUsage: {
+
type: "object",
+
properties: {
+
heapUsed: { type: "number", description: "MB" },
+
heapTotal: { type: "number", description: "MB" },
+
percentage: { type: "number" },
+
},
+
},
+
},
+
},
},
}),
apiResponse(503, "Service is unhealthy"),