Started PostgreSQL — data directory was uninitialized, ran initdb, set password, switched to md5 auth, all 24 migrations applied.
Build & Auto-Repair / build (push) Has been cancelled

Hardened db.ts — added statement_timeout: 30000, idle_in_transaction_session_timeout: 10000, created transaction() helper (BEGIN/COMMIT/ROLLBACK).
Multi-step API routes wrapped in transactions — Events POST, Conversations POST use atomic blocks.
Fixed leads pagination — status filter pushed into SQL WHERE (no client-side filtering after LIMIT/OFFSET), added SELECT COUNT(*) for total.
Rewrote dashboard — SQL aggregations (COUNT + GROUP BY + date_trunc) replace loading all leads into memory.
Optimized conversations GET — merged duplicate correlated subqueries into single LEFT JOIN LATERAL.
Created migration 021_performance_indexes.sql — 8 missing indexes + set_session_user_context() function caching current_user_hierarchy_level() as session variable (avoids per-query RLS join).
Fixed build error — duplicate const other in conversations route. Also fixed a TypeScript never error in dashboard breakdown map.
Verified — npx tsc --noEmit clean, npm test (13 pass, 7 integration skip gracefully), npx next build succeeds.
This commit is contained in:
2026-07-06 13:36:48 +02:00
parent 80dee367e8
commit bc83af8e00
28 changed files with 2274 additions and 352 deletions
+79 -93
View File
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server"
import { getSessionUser } from "@/lib/auth"
import { query } from "@/lib/db"
import { query, transaction } from "@/lib/db"
import { sendEventConfirmation } from "@/lib/email"
export async function GET(request: NextRequest) {
@@ -119,87 +119,85 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "Start time is required for this event type" }, { status: 400 })
}
const result = await query(
`INSERT INTO scheduled_events (user_id, participant_id, developer_id, lead_id, conversation_id, title, description, event_type, start_time, end_time, duration_minutes, client_name, client_email, client_phone)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING id, user_id, participant_id, developer_id, lead_id, conversation_id, title, description, participant_notes, event_type, start_time, end_time, duration_minutes, client_name, client_email, client_phone, status, created_at`,
[
user.id,
participantId || null,
developerId || null,
leadId || null,
conversationId || null,
title,
description || null,
eventType || "website_creation",
startTime || null,
eventType === "website_creation" ? null : (endTime || null),
eventType === "website_creation" ? null : (durationMinutes || null),
clientName || null,
clientEmail || null,
clientPhone || null,
],
user.id,
)
// Single transaction for all DB operations
const result = await transaction(async (client) => {
// 1. Insert event
const ins = await client.query(
`INSERT INTO scheduled_events (user_id, participant_id, developer_id, lead_id, conversation_id, title, description, event_type, start_time, end_time, duration_minutes, client_name, client_email, client_phone)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING id, user_id, participant_id, developer_id, lead_id, conversation_id, title, description, participant_notes, event_type, start_time, end_time, duration_minutes, client_name, client_email, client_phone, status, created_at`,
[
user.id, participantId || null, developerId || null, leadId || null, conversationId || null,
title, description || null, eventType || "website_creation", startTime || null,
eventType === "website_creation" ? null : (endTime || null),
eventType === "website_creation" ? null : (durationMinutes || null),
clientName || null, clientEmail || null, clientPhone || null,
],
)
const r = ins.rows[0]
const r = result.rows[0]
// 2. Auto-update lead stage if website creation event
if (r.event_type === "website_creation" && leadId) {
const stageResult = await client.query("SELECT id FROM lead_stages WHERE name = 'Qualified'")
if (stageResult.rows.length > 0) {
await client.query(
"UPDATE leads SET stage_id = $1, updated_at = NOW() WHERE id = $2 AND deleted_at IS NULL",
[stageResult.rows[0].id, leadId],
)
}
}
// Auto-update lead to "pending" when a website creation event is created
if (r.event_type === "website_creation" && leadId) {
const stageResult = await query("SELECT id FROM lead_stages WHERE name = 'Qualified'")
if (stageResult.rows.length > 0) {
await query(
`UPDATE leads SET stage_id = $1, updated_at = NOW() WHERE id = $2 AND deleted_at IS NULL`,
[stageResult.rows[0].id, leadId],
// 3. Batch notifications (multi-row INSERT)
const notifications: { user_id: string; type: string; title: string; description: string; link: string; context_id: string; context_type: string }[] = []
if (participantId && participantId !== user.id) {
notifications.push({
user_id: participantId, type: "event_scheduled",
title: `Meeting scheduled: ${title}`,
description: `${user.firstName} ${user.lastName} scheduled a ${eventType || "meeting"} with you`,
link: "/calendar", context_id: r.id, context_type: "scheduled_event",
})
}
if (developerId && developerId !== user.id) {
notifications.push({
user_id: developerId, type: "event_scheduled",
title: `Project assigned: ${title}`,
description: `${user.firstName} ${user.lastName} assigned a project to you`,
link: "/calendar", context_id: r.id, context_type: "scheduled_event",
})
}
if (notifications.length > 0) {
const placeholders = notifications.map((_, i) =>
`($${i * 7 + 1}, $${i * 7 + 2}, $${i * 7 + 3}, $${i * 7 + 4}, $${i * 7 + 5}, $${i * 7 + 6}, $${i * 7 + 7})`
).join(", ")
const flatParams = notifications.flatMap(n => [n.user_id, n.type, n.title, n.description, n.link, n.context_id, n.context_type])
await client.query(
`INSERT INTO notifications (user_id, type, title, description, link, context_id, context_type) VALUES ${placeholders}`,
flatParams,
)
}
}
if (participantId && participantId !== user.id) {
await query(
`INSERT INTO notifications (user_id, type, title, description, link, context_id, context_type)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[
participantId,
"event_scheduled",
`Meeting scheduled: ${title}`,
`${user.firstName} ${user.lastName} scheduled a ${eventType || "meeting"} with you`,
`/calendar`,
r.id,
"scheduled_event",
],
)
}
// 4. Fetch participant + developer in one query
const idsToFetch = [participantId, developerId].filter(Boolean) as string[]
let usersMap: Record<string, { id: string; first_name: string; last_name: string; email: string }> = {}
if (idsToFetch.length > 0) {
const userPlaceholders = idsToFetch.map((_, i) => `$${i + 1}`).join(", ")
const userRows = await client.query(
`SELECT id, first_name, last_name, email FROM users WHERE id IN (${userPlaceholders})`,
idsToFetch,
)
for (const row of userRows.rows) {
usersMap[row.id] = row
}
}
// Notify developer
if (developerId && developerId !== user.id) {
await query(
`INSERT INTO notifications (user_id, type, title, description, link, context_id, context_type)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
[
developerId,
"event_scheduled",
`Project assigned: ${title}`,
`${user.firstName} ${user.lastName} assigned a project to you`,
`/calendar`,
r.id,
"scheduled_event",
],
)
}
return { r, usersMap }
}, user.id)
const participantResult = participantId ? await query(
`SELECT email, first_name, last_name FROM users WHERE id = $1`,
[participantId],
) : null
const participant = participantResult?.rows[0] || null
const developerResult = developerId ? await query(
`SELECT id, first_name, last_name, email FROM users WHERE id = $1`,
[developerId],
) : null
const dev = developerResult?.rows[0] || null
const { r, usersMap } = result
const participant = participantId ? usersMap[participantId] || null : null
const dev = developerId ? usersMap[developerId] || null : null
// Email sent asynchronously outside transaction (side effect)
sendEventConfirmation({
creatorName: `${user.firstName} ${user.lastName}`,
creatorEmail: user.email,
@@ -215,28 +213,16 @@ export async function POST(request: NextRequest) {
return NextResponse.json({
event: {
id: r.id,
userId: r.user_id,
participantId: r.participant_id,
developerId: r.developer_id,
leadId: r.lead_id,
conversationId: r.conversation_id,
title: r.title,
description: r.description,
participantNotes: r.participant_notes,
eventType: r.event_type,
startTime: r.start_time,
endTime: r.end_time,
durationMinutes: r.duration_minutes,
status: r.status,
id: r.id, userId: r.user_id, participantId: r.participant_id, developerId: r.developer_id,
leadId: r.lead_id, conversationId: r.conversation_id,
title: r.title, description: r.description, participantNotes: r.participant_notes,
eventType: r.event_type, startTime: r.start_time, endTime: r.end_time,
durationMinutes: r.duration_minutes, status: r.status, createdAt: r.created_at,
creator: { id: user.id, name: `${user.firstName} ${user.lastName}`, email: user.email, role: user.role },
participant: participantId ? { id: participantId, name: participant ? `${participant.first_name} ${participant.last_name}` : participantId, email: participant?.email || null } : null,
participant: participant ? { id: participantId, name: `${participant.first_name} ${participant.last_name}`, email: participant.email } : null,
developer: dev ? { id: dev.id, name: `${dev.first_name} ${dev.last_name}`, email: dev.email } : null,
lead: leadId ? { id: leadId, companyName: "", contactName: "" } : null,
clientName: r.client_name || null,
clientEmail: r.client_email || null,
clientPhone: r.client_phone || null,
createdAt: r.created_at,
clientName: r.client_name || null, clientEmail: r.client_email || null, clientPhone: r.client_phone || null,
},
}, { status: 201 })
} catch (error) {