feat: global API rate limiting + webhook dispatcher wired to key events
Rate limiting (middleware-level): - 120 requests/minute per IP for all /api/* routes - Returns 429 with retry-after header when exceeded - In-memory store (per-container, sufficient for single-instance) Webhook dispatcher wired to events: - license.created → fires when a new license is issued - ticket.created → fires when a support ticket is created - instance.provisioned → fires when a tenant instance goes live - Each event includes relevant payload (key, product, URL, etc.) - Fire-and-forget (doesn't block the response) - HMAC-SHA256 signed (existing webhook.ts implementation) Also: - Ticket creation now sends email notification (sendTicketCreatedEmail)
This commit is contained in:
1 parent
985137b802
commit
db8e365932
4 files changed
+83
No files matched your search
@@ -321,6 +321,18 @@ export async function POST(req: Request) {
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// Dispatch webhook event
|
||||
const { dispatchWebhook } = await import('@/lib/webhook')
|
||||
dispatchWebhook('instance.provisioned', {
|
||||
instanceId: instance.id,
|
||||
subdomain,
|
||||
url: result.url,
|
||||
product,
|
||||
companyId: license.companyId,
|
||||
status: isDemo ? 'demo_active' : 'active',
|
||||
demoExpiresAt: instance.demoExpiresAt,
|
||||
}).catch(() => {})
|
||||
|
||||
return NextResponse.json({
|
||||
instanceId: instance.id,
|
||||
subdomain,
|
||||
|
||||
@@ -316,6 +316,20 @@ export async function POST(req: Request) {
|
||||
}).catch(() => {}) // fire-and-forget, don't block response
|
||||
}
|
||||
|
||||
// Dispatch webhook event
|
||||
const { dispatchWebhook } = await import('@/lib/webhook')
|
||||
dispatchWebhook('license.created', {
|
||||
licenseId: license.id,
|
||||
key,
|
||||
product,
|
||||
plan,
|
||||
companyId: license.companyId,
|
||||
companyName: company.name,
|
||||
environment,
|
||||
billingCycle,
|
||||
netPriceCents: pricing.netPriceCents,
|
||||
}).catch(() => {})
|
||||
|
||||
return NextResponse.json({ license, pricing: pricing.breakdown }, { status: 201 })
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'internal server error'
|
||||
|
||||
@@ -159,6 +159,28 @@ export async function POST(req: Request) {
|
||||
},
|
||||
})
|
||||
|
||||
// Send ticket created email
|
||||
const { sendTicketCreatedEmail } = await import('@/lib/email')
|
||||
if (company.email) {
|
||||
sendTicketCreatedEmail({
|
||||
to: company.email,
|
||||
ticketNumber: ticket.number,
|
||||
subject: ticket.subject,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// Dispatch webhook event
|
||||
const { dispatchWebhook } = await import('@/lib/webhook')
|
||||
dispatchWebhook('ticket.created', {
|
||||
ticketId: ticket.id,
|
||||
number: ticket.number,
|
||||
subject: ticket.subject,
|
||||
companyId,
|
||||
priority,
|
||||
routedToAgency,
|
||||
agencyId,
|
||||
}).catch(() => {})
|
||||
|
||||
return NextResponse.json({ ticket }, { status: 201 })
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'internal server error'
|
||||
|
||||
Reference in new issue
Block a user