codeflash-internal/js/cf-api/endpoints/slack-events.ts
Kevin Turcios d7a8b8f227
perf: fix CI build + lazy-load heavy libs + parallelize DB queries (#2601)
## Summary
- **Fix CI build failure**: Auth0Client crashes during Next.js
prerendering when env vars aren't set. Returns a no-op stub (`getSession
→ null`) when domain is missing — semantically correct for static
generation
- **Lazy-load markdown libs (~260kb)**: ReactMarkdown, remarkGfm, and
react-syntax-highlighter were eagerly imported in monaco-diff-viewer but
only rendered when user expands "Generated Tests". Extracted into a
dynamic component
- **Parallelize repo detail query**: `getRepositoryById` ran the
activity count sequentially after the repo lookup. Since `repoId` is
already available, all three queries now run in parallel

## Test plan
- [ ] CI `build` check passes (was failing since #2598)
- [ ] Trace page still renders generated tests correctly when expanded
- [ ] Repository detail page loads correctly with activity status
2026-04-13 11:03:05 -05:00

151 lines
4.6 KiB
TypeScript

import { Request, Response } from "express"
import * as crypto from "node:crypto"
import { posthog } from "../analytics.js"
import { processReaction } from "../github/optimization_approval.js"
import * as Sentry from "@sentry/node"
import { logger } from "../utils/logger.js"
import { forbidden, internalServerError } from "../exceptions/index.js"
// Dependencies interface for easier testing
export interface HandleSlackEventsDependencies {
crypto: typeof crypto
posthog: typeof posthog
processReaction: typeof processReaction
Sentry: typeof Sentry
getSlackSigningSecret: () => string | undefined
getCurrentTime: () => number
console: typeof console
}
// Default dependencies
let dependencies: HandleSlackEventsDependencies = {
crypto,
posthog,
processReaction,
Sentry,
getSlackSigningSecret: () => process.env.SLACK_SIGNING_SECRET,
getCurrentTime: () => Math.floor(Date.now() / 1000),
console,
}
// For testing - allow dependency injection
export function setHandleSlackEventsDependencies(deps: Partial<HandleSlackEventsDependencies>) {
dependencies = { ...dependencies, ...deps }
}
export function resetHandleSlackEventsDependencies() {
dependencies = {
crypto,
posthog,
processReaction,
Sentry,
getSlackSigningSecret: () => process.env.SLACK_SIGNING_SECRET,
getCurrentTime: () => Math.floor(Date.now() / 1000),
console,
}
}
/**
* Verify that the request is coming from Slack
*/
export function verifySlackRequest(req: Request): boolean {
const SLACK_SIGNING_SECRET = dependencies.getSlackSigningSecret()
if (!SLACK_SIGNING_SECRET) {
logger.warn("SLACK_SIGNING_SECRET not configured. Skipping request verification.", req)
return true
}
const slackSignature = req.headers["x-slack-signature"] as string
const slackTimestamp = req.headers["x-slack-request-timestamp"] as string
if (!slackSignature || !slackTimestamp) {
return false
}
// Prevent replay attacks - reject requests older than 5 minutes
const currentTime = dependencies.getCurrentTime()
if (Math.abs(currentTime - parseInt(slackTimestamp)) > 300) {
return false
}
const requestBody = typeof req.body === "string" ? req.body : JSON.stringify(req.body)
const baseString = `v0:${slackTimestamp}:${requestBody}`
const hmac = dependencies.crypto.createHmac("sha256", SLACK_SIGNING_SECRET)
const signature = `v0=${hmac.update(baseString).digest("hex")}`
return dependencies.crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(slackSignature))
}
/**
* Handle Slack events for the approval system
*/
export async function handleSlackEvents(req: Request, res: Response) {
try {
// Verify the request is from Slack
if (!verifySlackRequest(req)) {
logger.error("Failed to verify Slack request", req)
throw forbidden("Invalid Slack request signature")
}
// Handle URL verification (required when setting up events)
if (req.body.type === "url_verification") {
logger.info("Handling Slack URL verification challenge", req)
return res.json({ challenge: req.body.challenge })
}
// Acknowledge receipt quickly
res.status(200).send("OK")
const event = req.body.event
if (event) {
logger.info(`Processing Slack event: ${event.type}`, req, {
eventType: event.type,
})
if (event.type === "reaction_added") {
const processed = await dependencies.processReaction(event)
if (processed) {
dependencies.posthog?.capture({
distinctId: "system",
event: "slack-approval-reaction-processed",
properties: {
reaction: event.reaction,
result: "success",
},
})
}
}
}
} catch (error) {
// Re-throw AppExceptions to be handled by GlobalExceptionHandler if headers not sent
if (error && typeof error === "object" && "getHttpStatus" in error) {
if (!res.headersSent) {
throw error
}
// If headers already sent, just log it
logger.errorWithSentry(`Error handling Slack event: ${error}`, req, {}, error as Error)
dependencies.Sentry.captureException(error)
return
}
logger.errorWithSentry(`Error handling Slack event: ${error}`, req, {}, error as Error)
// If we haven't sent a response yet, throw an error
if (!res.headersSent) {
throw internalServerError("Error processing Slack event")
}
dependencies.Sentry.captureException(error)
// Log to monitoring
dependencies.posthog?.capture({
distinctId: "system",
event: "slack-optimization-approval-error",
properties: {
error: String(error),
},
})
}
}