a cache for slack profile pictures and emojis
at main 12 kB view raw
1/** 2 * Complete typed route definitions for all Cachet API endpoints 3 */ 4 5import type { SlackCache } from "../cache"; 6import * as handlers from "../handlers"; 7import { createAnalyticsWrapper } from "../lib/analytics-wrapper"; 8import type { SlackWrapper } from "../slackWrapper"; 9import { 10 apiResponse, 11 createRoute, 12 pathParam, 13 queryParam, 14} from "../types/routes"; 15 16// Factory function to create all routes with injected dependencies 17export function createApiRoutes(cache: SlackCache, slackApp: SlackWrapper) { 18 // Inject dependencies into handlers 19 handlers.injectDependencies(cache, slackApp); 20 21 const withAnalytics = createAnalyticsWrapper(cache); 22 23 return { 24 "/health": { 25 GET: createRoute( 26 withAnalytics("/health", "GET", handlers.handleHealthCheck), 27 { 28 summary: "Health check", 29 description: 30 "Check if the service is healthy and operational. Add ?detailed=true for comprehensive health information including Slack API status, queue depth, and memory usage.", 31 tags: ["Health"], 32 parameters: { 33 query: [ 34 queryParam( 35 "detailed", 36 "boolean", 37 "Return detailed health check information", 38 false, 39 false, 40 ), 41 ], 42 }, 43 responses: Object.fromEntries([ 44 apiResponse(200, "Service is healthy", { 45 type: "object", 46 properties: { 47 status: { 48 type: "string", 49 example: "healthy", 50 enum: ["healthy", "degraded", "unhealthy"], 51 }, 52 cache: { type: "boolean", example: true }, 53 uptime: { type: "number", example: 123456 }, 54 checks: { 55 type: "object", 56 description: "Detailed checks (only with ?detailed=true)", 57 properties: { 58 database: { 59 type: "object", 60 properties: { 61 status: { type: "boolean" }, 62 latency: { type: "number", description: "ms" }, 63 }, 64 }, 65 slackApi: { 66 type: "object", 67 properties: { 68 status: { type: "boolean" }, 69 error: { type: "string" }, 70 }, 71 }, 72 queueDepth: { 73 type: "number", 74 description: "Number of users queued for update", 75 }, 76 memoryUsage: { 77 type: "object", 78 properties: { 79 heapUsed: { type: "number", description: "MB" }, 80 heapTotal: { type: "number", description: "MB" }, 81 percentage: { type: "number" }, 82 details: { 83 type: "object", 84 properties: { 85 heapUsedMiB: { 86 type: "number", 87 description: "Precise heap used in MiB", 88 }, 89 heapTotalMiB: { 90 type: "number", 91 description: "Precise heap total in MiB", 92 }, 93 heapPercent: { 94 type: "number", 95 description: "Precise heap percentage", 96 }, 97 rssMiB: { 98 type: "number", 99 description: "Resident Set Size in MiB", 100 }, 101 externalMiB: { 102 type: "number", 103 description: "External memory in MiB", 104 }, 105 arrayBuffersMiB: { 106 type: "number", 107 description: "Array buffers in MiB", 108 }, 109 }, 110 }, 111 }, 112 }, 113 }, 114 }, 115 }, 116 }), 117 apiResponse(503, "Service is unhealthy"), 118 ]), 119 }, 120 ), 121 }, 122 123 "/users/:id": { 124 GET: createRoute( 125 withAnalytics("/users/:id", "GET", handlers.handleGetUser), 126 { 127 summary: "Get user information", 128 description: "Retrieve cached user profile information from Slack", 129 tags: ["Users"], 130 parameters: { 131 path: [pathParam("id", "string", "Slack user ID", "U062UG485EE")], 132 }, 133 responses: Object.fromEntries([ 134 apiResponse(200, "User information retrieved successfully", { 135 type: "object", 136 properties: { 137 id: { type: "string", example: "U062UG485EE" }, 138 userId: { type: "string", example: "U062UG485EE" }, 139 displayName: { type: "string", example: "Kieran Klukas" }, 140 pronouns: { type: "string", example: "he/him" }, 141 imageUrl: { 142 type: "string", 143 example: "https://avatars.slack-edge.com/...", 144 }, 145 }, 146 }), 147 apiResponse(404, "User not found"), 148 ]), 149 }, 150 ), 151 }, 152 153 "/users/:id/r": { 154 GET: createRoute( 155 withAnalytics("/users/:id/r", "GET", handlers.handleUserRedirect), 156 { 157 summary: "Redirect to user profile image", 158 description: "Direct redirect to the user's cached profile image URL", 159 tags: ["Users"], 160 parameters: { 161 path: [pathParam("id", "string", "Slack user ID", "U062UG485EE")], 162 }, 163 responses: Object.fromEntries([ 164 apiResponse(302, "Redirect to user image"), 165 apiResponse(307, "Temporary redirect to default avatar"), 166 apiResponse(404, "User not found"), 167 ]), 168 }, 169 ), 170 }, 171 172 "/users/:id/purge": { 173 POST: createRoute( 174 withAnalytics("/users/:id/purge", "POST", handlers.handlePurgeUser), 175 { 176 summary: "Purge user cache", 177 description: 178 "Remove a specific user from the cache (requires authentication)", 179 tags: ["Users", "Admin"], 180 requiresAuth: true, 181 parameters: { 182 path: [ 183 pathParam( 184 "id", 185 "string", 186 "Slack user ID to purge", 187 "U062UG485EE", 188 ), 189 ], 190 }, 191 responses: Object.fromEntries([ 192 apiResponse(200, "User cache purged successfully", { 193 type: "object", 194 properties: { 195 message: { type: "string", example: "User cache purged" }, 196 userId: { type: "string", example: "U062UG485EE" }, 197 success: { type: "boolean", example: true }, 198 }, 199 }), 200 apiResponse(401, "Unauthorized"), 201 ]), 202 }, 203 ), 204 }, 205 206 "/emojis": { 207 GET: createRoute( 208 withAnalytics("/emojis", "GET", handlers.handleListEmojis), 209 { 210 summary: "List all emojis", 211 description: 212 "Get a list of all cached custom emojis from the Slack workspace", 213 tags: ["Emojis"], 214 responses: Object.fromEntries([ 215 apiResponse(200, "List of emojis retrieved successfully", { 216 type: "array", 217 items: { 218 type: "object", 219 properties: { 220 name: { type: "string", example: "hackshark" }, 221 imageUrl: { 222 type: "string", 223 example: "https://emoji.slack-edge.com/...", 224 }, 225 alias: { type: "string", nullable: true, example: null }, 226 }, 227 }, 228 }), 229 ]), 230 }, 231 ), 232 }, 233 234 "/emojis/:name": { 235 GET: createRoute( 236 withAnalytics("/emojis/:name", "GET", handlers.handleGetEmoji), 237 { 238 summary: "Get emoji information", 239 description: "Retrieve information about a specific custom emoji", 240 tags: ["Emojis"], 241 parameters: { 242 path: [ 243 pathParam( 244 "name", 245 "string", 246 "Emoji name (without colons)", 247 "hackshark", 248 ), 249 ], 250 }, 251 responses: Object.fromEntries([ 252 apiResponse(200, "Emoji information retrieved successfully", { 253 type: "object", 254 properties: { 255 name: { type: "string", example: "hackshark" }, 256 imageUrl: { 257 type: "string", 258 example: "https://emoji.slack-edge.com/...", 259 }, 260 alias: { type: "string", nullable: true, example: null }, 261 }, 262 }), 263 apiResponse(404, "Emoji not found"), 264 ]), 265 }, 266 ), 267 }, 268 269 "/emojis/:name/r": { 270 GET: createRoute( 271 withAnalytics("/emojis/:name/r", "GET", handlers.handleEmojiRedirect), 272 { 273 summary: "Redirect to emoji image", 274 description: "Direct redirect to the emoji's cached image URL", 275 tags: ["Emojis"], 276 parameters: { 277 path: [ 278 pathParam( 279 "name", 280 "string", 281 "Emoji name (without colons)", 282 "hackshark", 283 ), 284 ], 285 }, 286 responses: Object.fromEntries([ 287 apiResponse(302, "Redirect to emoji image"), 288 apiResponse(404, "Emoji not found"), 289 ]), 290 }, 291 ), 292 }, 293 294 "/reset": { 295 POST: createRoute( 296 withAnalytics("/reset", "POST", handlers.handleResetCache), 297 { 298 summary: "Reset entire cache", 299 description: "Clear all cached data (requires authentication)", 300 tags: ["Admin"], 301 requiresAuth: true, 302 responses: Object.fromEntries([ 303 apiResponse(200, "Cache reset successfully", { 304 type: "object", 305 properties: { 306 message: { type: "string", example: "Cache has been reset" }, 307 users: { type: "number", example: 42 }, 308 emojis: { type: "number", example: 1337 }, 309 }, 310 }), 311 apiResponse(401, "Unauthorized"), 312 ]), 313 }, 314 ), 315 }, 316 317 "/api/stats/essential": { 318 GET: createRoute( 319 withAnalytics( 320 "/api/stats/essential", 321 "GET", 322 handlers.handleGetEssentialStats, 323 ), 324 { 325 summary: "Get essential analytics", 326 description: "Fast-loading essential statistics for the dashboard", 327 tags: ["Analytics"], 328 parameters: { 329 query: [ 330 queryParam( 331 "days", 332 "number", 333 "Number of days to analyze", 334 false, 335 7, 336 ), 337 ], 338 }, 339 responses: Object.fromEntries([ 340 apiResponse(200, "Essential stats retrieved successfully", { 341 type: "object", 342 properties: { 343 totalRequests: { type: "number", example: 12345 }, 344 averageResponseTime: { type: "number", example: 23.5 }, 345 uptime: { type: "number", example: 99.9 }, 346 period: { type: "string", example: "7 days" }, 347 }, 348 }), 349 ]), 350 }, 351 ), 352 }, 353 354 "/api/stats/charts": { 355 GET: createRoute( 356 withAnalytics("/api/stats/charts", "GET", handlers.handleGetChartData), 357 { 358 summary: "Get chart data", 359 description: "Time-series data for request and latency charts", 360 tags: ["Analytics"], 361 parameters: { 362 query: [ 363 queryParam( 364 "days", 365 "number", 366 "Number of days to analyze", 367 false, 368 7, 369 ), 370 ], 371 }, 372 responses: Object.fromEntries([ 373 apiResponse(200, "Chart data retrieved successfully", { 374 type: "array", 375 items: { 376 type: "object", 377 properties: { 378 time: { type: "string", example: "2024-01-01T12:00:00Z" }, 379 count: { type: "number", example: 42 }, 380 averageResponseTime: { type: "number", example: 25.3 }, 381 }, 382 }, 383 }), 384 ]), 385 }, 386 ), 387 }, 388 389 "/api/stats/useragents": { 390 GET: createRoute( 391 withAnalytics( 392 "/api/stats/useragents", 393 "GET", 394 handlers.handleGetUserAgents, 395 ), 396 { 397 summary: "Get user agents statistics", 398 description: 399 "List of user agents accessing the service with request counts", 400 tags: ["Analytics"], 401 parameters: { 402 query: [ 403 queryParam( 404 "days", 405 "number", 406 "Number of days to analyze", 407 false, 408 7, 409 ), 410 ], 411 }, 412 responses: Object.fromEntries([ 413 apiResponse(200, "User agents data retrieved successfully", { 414 type: "array", 415 items: { 416 type: "object", 417 properties: { 418 userAgent: { type: "string", example: "Mozilla/5.0..." }, 419 count: { type: "number", example: 123 }, 420 }, 421 }, 422 }), 423 ]), 424 }, 425 ), 426 }, 427 428 "/stats": { 429 GET: createRoute( 430 withAnalytics("/stats", "GET", handlers.handleGetStats), 431 { 432 summary: "Get complete analytics (legacy)", 433 description: 434 "Legacy endpoint returning all analytics data in one response", 435 tags: ["Analytics", "Legacy"], 436 parameters: { 437 query: [ 438 queryParam( 439 "days", 440 "number", 441 "Number of days to analyze", 442 false, 443 7, 444 ), 445 ], 446 }, 447 responses: Object.fromEntries([ 448 apiResponse(200, "Complete analytics data retrieved", { 449 type: "object", 450 properties: { 451 totalRequests: { type: "number" }, 452 averageResponseTime: { type: "number" }, 453 chartData: { type: "array" }, 454 userAgents: { type: "array" }, 455 }, 456 }), 457 ]), 458 }, 459 ), 460 }, 461 }; 462}