mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-02-13 17:55:46 +01:00
- Fix syntax errors in skills markdown files (.github/skills, .opencode/skills) - Change typescript to tsx for code blocks with JSX - Replace ellipsis (...) in array examples with valid syntax - Separate CSS from TypeScript into distinct code blocks - Convert JavaScript object examples to valid JSON in docs - Fix enum definitions with proper comma separation
148 lines
5.2 KiB
TypeScript
148 lines
5.2 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import { neonAuth } from "@/lib/auth/server";
|
|
import { fetchAndParseCsv } from "@/lib/csvFetcher";
|
|
import { processQueuedImports } from "@/lib/importProcessor";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
// MIGRATED: Removed "export const dynamic = 'force-dynamic'" - dynamic by default with Cache Components
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
// Authenticate user
|
|
const { session: authSession, user: authUser } = await neonAuth();
|
|
if (!authSession || !authUser?.email) {
|
|
return NextResponse.json({ error: "Not logged in" }, { status: 401 });
|
|
}
|
|
|
|
// Look up user to get companyId and role
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: authUser.email },
|
|
select: { companyId: true, role: true },
|
|
});
|
|
|
|
if (!user || !user.companyId) {
|
|
return NextResponse.json(
|
|
{ error: "User not found or no company" },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
// Check if user has ADMIN role
|
|
if (user.role !== "ADMIN") {
|
|
return NextResponse.json(
|
|
{ error: "Admin access required" },
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
// Use user's companyId from auth (ignore any body params for security)
|
|
const companyId = user.companyId;
|
|
|
|
const company = await prisma.company.findUnique({
|
|
where: { id: companyId },
|
|
});
|
|
if (!company) {
|
|
return NextResponse.json({ error: "Company not found" }, { status: 404 });
|
|
}
|
|
|
|
// Check if company is active and can process data
|
|
if (company.status !== "ACTIVE") {
|
|
return NextResponse.json(
|
|
{
|
|
error: `Data processing is disabled for ${company.status.toLowerCase()} companies`,
|
|
companyStatus: company.status,
|
|
},
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
|
|
const rawSessionData = await fetchAndParseCsv(
|
|
company.csvUrl,
|
|
company.csvUsername as string | undefined,
|
|
company.csvPassword as string | undefined
|
|
);
|
|
|
|
let importedCount = 0;
|
|
|
|
// Create SessionImport records for new data
|
|
for (const rawSession of rawSessionData) {
|
|
try {
|
|
// Use upsert to handle duplicates gracefully
|
|
await prisma.sessionImport.upsert({
|
|
where: {
|
|
companyId_externalSessionId: {
|
|
companyId: company.id,
|
|
externalSessionId: rawSession.externalSessionId,
|
|
},
|
|
},
|
|
update: {
|
|
// Update existing record with latest data
|
|
startTimeRaw: rawSession.startTimeRaw,
|
|
endTimeRaw: rawSession.endTimeRaw,
|
|
ipAddress: rawSession.ipAddress,
|
|
countryCode: rawSession.countryCode,
|
|
language: rawSession.language,
|
|
messagesSent: rawSession.messagesSent,
|
|
sentimentRaw: rawSession.sentimentRaw,
|
|
escalatedRaw: rawSession.escalatedRaw,
|
|
forwardedHrRaw: rawSession.forwardedHrRaw,
|
|
fullTranscriptUrl: rawSession.fullTranscriptUrl,
|
|
avgResponseTimeSeconds: rawSession.avgResponseTimeSeconds,
|
|
tokens: rawSession.tokens,
|
|
tokensEur: rawSession.tokensEur,
|
|
category: rawSession.category,
|
|
initialMessage: rawSession.initialMessage,
|
|
// Status tracking now handled by ProcessingStatusManager
|
|
},
|
|
create: {
|
|
companyId: company.id,
|
|
externalSessionId: rawSession.externalSessionId,
|
|
startTimeRaw: rawSession.startTimeRaw,
|
|
endTimeRaw: rawSession.endTimeRaw,
|
|
ipAddress: rawSession.ipAddress,
|
|
countryCode: rawSession.countryCode,
|
|
language: rawSession.language,
|
|
messagesSent: rawSession.messagesSent,
|
|
sentimentRaw: rawSession.sentimentRaw,
|
|
escalatedRaw: rawSession.escalatedRaw,
|
|
forwardedHrRaw: rawSession.forwardedHrRaw,
|
|
fullTranscriptUrl: rawSession.fullTranscriptUrl,
|
|
avgResponseTimeSeconds: rawSession.avgResponseTimeSeconds,
|
|
tokens: rawSession.tokens,
|
|
tokensEur: rawSession.tokensEur,
|
|
category: rawSession.category,
|
|
initialMessage: rawSession.initialMessage,
|
|
// Status tracking now handled by ProcessingStatusManager
|
|
},
|
|
});
|
|
importedCount++;
|
|
} catch (error) {
|
|
// Log individual session import errors but continue processing
|
|
process.stderr.write(
|
|
`Failed to import session ${rawSession.externalSessionId}: ${error}\n`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Immediately process the queued imports to create Session records
|
|
console.log("[Refresh API] Processing queued imports...");
|
|
await processQueuedImports(100); // Process up to 100 imports immediately
|
|
|
|
// Count how many sessions were created
|
|
const sessionCount = await prisma.session.count({
|
|
where: { companyId: company.id },
|
|
});
|
|
|
|
return NextResponse.json({
|
|
ok: true,
|
|
imported: importedCount,
|
|
total: rawSessionData.length,
|
|
sessions: sessionCount,
|
|
message: `Successfully imported ${importedCount} records and processed them into sessions. Total sessions: ${sessionCount}`,
|
|
});
|
|
} catch (e) {
|
|
const error = e instanceof Error ? e.message : "An unknown error occurred";
|
|
return NextResponse.json({ error }, { status: 500 });
|
|
}
|
|
}
|