mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-02-13 12:55:42 +01:00
fix: comprehensive TypeScript/build fixes and modernization
- Update tsconfig to ES2024 target and bundler moduleResolution - Add dynamic imports for chart.js and recharts (bundle optimization) - Consolidate 17 useState into useReducer in sessions page - Fix 18 .js extension imports across lib files - Add type declarations for @rapideditor/country-coder - Fix platform user types (PlatformUserRole enum) - Fix Calendar component prop types - Centralize next-auth type augmentation - Add force-dynamic to all API routes (prevent build-time prerender) - Fix Prisma JSON null handling with Prisma.DbNull - Fix various type mismatches (SessionMessage, ImportRecord, etc.) - Export ButtonProps from button component - Update next-themes import path - Replace JSX.Element with React.ReactElement - Remove obsolete debug scripts and pnpm lockfile - Downgrade eslint to v8 for next compatibility
This commit is contained in:
33
lib/auth.ts
33
lib/auth.ts
@@ -3,34 +3,7 @@ import type { NextAuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
// Define the shape of the JWT token
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
companyId: string;
|
||||
role: string;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the shape of the session object
|
||||
declare module "next-auth" {
|
||||
interface Session {
|
||||
user: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
companyId?: string;
|
||||
role?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
companyId: string;
|
||||
role: string;
|
||||
}
|
||||
}
|
||||
// Type augmentation moved to types/next-auth.d.ts
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
@@ -50,13 +23,13 @@ export const authOptions: NextAuthOptions = {
|
||||
include: { company: true },
|
||||
});
|
||||
|
||||
if (!user || !user.hashedPassword) {
|
||||
if (!user || !user.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password,
|
||||
user.hashedPassword
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { Pool } from "pg";
|
||||
import { env } from "./env.js";
|
||||
import { env } from "./env";
|
||||
|
||||
// Enhanced connection pool configuration
|
||||
const createConnectionPool = () => {
|
||||
|
||||
@@ -213,9 +213,9 @@ export function createErrorResponse(error: AppError) {
|
||||
error.validationErrors && {
|
||||
validationErrors: error.validationErrors,
|
||||
}),
|
||||
...(error instanceof ResourceNotFoundError &&
|
||||
...(error instanceof NotFoundError &&
|
||||
error.resource && { resource: error.resource }),
|
||||
...(error instanceof ResourceNotFoundError &&
|
||||
...(error instanceof NotFoundError &&
|
||||
error.resourceId && {
|
||||
resourceId: error.resourceId,
|
||||
}),
|
||||
|
||||
@@ -1,41 +1,29 @@
|
||||
// SessionImport to Session processor
|
||||
import type { Prisma, SessionImport } from "@prisma/client";
|
||||
import { ProcessingStage, SentimentCategory } from "@prisma/client";
|
||||
import cron from "node-cron";
|
||||
import { withRetry } from "./database-retry.js";
|
||||
import { withRetry } from "./database-retry";
|
||||
import { getSchedulerConfig } from "./env";
|
||||
import { prisma } from "./prisma.js";
|
||||
import { prisma } from "./prisma";
|
||||
import {
|
||||
completeStage,
|
||||
failStage,
|
||||
initializeSession,
|
||||
skipStage,
|
||||
startStage,
|
||||
} from "./processingStatusManager.js";
|
||||
} from "./processingStatusManager";
|
||||
import {
|
||||
fetchTranscriptContent,
|
||||
isValidTranscriptUrl,
|
||||
} from "./transcriptFetcher";
|
||||
|
||||
interface ImportRecord {
|
||||
id: string;
|
||||
companyId: string;
|
||||
startTimeRaw: string;
|
||||
endTimeRaw: string;
|
||||
externalSessionId: string;
|
||||
sessionId?: string;
|
||||
userId?: string;
|
||||
category?: string;
|
||||
language?: string;
|
||||
sentiment?: string;
|
||||
escalated?: boolean;
|
||||
forwardedHr?: boolean;
|
||||
avgResponseTime?: number;
|
||||
messagesSent?: number;
|
||||
fullTranscriptUrl?: string;
|
||||
rawTranscriptContent?: string;
|
||||
aiSummary?: string;
|
||||
initialMsg?: string;
|
||||
}
|
||||
type ImportRecord = SessionImport & {
|
||||
company: {
|
||||
id: string;
|
||||
csvUsername: string | null;
|
||||
csvPassword: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse European date format (DD.MM.YYYY HH:mm:ss) to JavaScript Date
|
||||
@@ -245,7 +233,7 @@ async function handleTranscriptFetching(
|
||||
);
|
||||
|
||||
if (transcriptResult.success) {
|
||||
transcriptContent = transcriptResult.content;
|
||||
transcriptContent = transcriptResult.content ?? null;
|
||||
console.log(
|
||||
`[Import Processor] ✓ Fetched transcript for ${importRecord.externalSessionId} (${transcriptContent?.length} chars)`
|
||||
);
|
||||
@@ -397,6 +385,15 @@ async function processQueuedImportsInternal(batchSize = 50): Promise<void> {
|
||||
status: "ACTIVE", // Only process imports from active companies
|
||||
},
|
||||
},
|
||||
include: {
|
||||
company: {
|
||||
select: {
|
||||
id: true,
|
||||
csvUsername: true,
|
||||
csvPassword: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
take: batchSize,
|
||||
orderBy: {
|
||||
createdAt: "asc", // Process oldest first
|
||||
@@ -429,7 +426,10 @@ async function processQueuedImportsInternal(batchSize = 50): Promise<void> {
|
||||
|
||||
// Process with concurrency limit to avoid overwhelming the database
|
||||
const concurrencyLimit = 5;
|
||||
const results = [];
|
||||
const results: Array<{
|
||||
importRecord: ImportRecord;
|
||||
result: { success: boolean; error?: string };
|
||||
}> = [];
|
||||
|
||||
for (let i = 0; i < batchPromises.length; i += concurrencyLimit) {
|
||||
const chunk = batchPromises.slice(i, i + concurrencyLimit);
|
||||
|
||||
@@ -3,39 +3,7 @@ import type { NextAuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
// Define the shape of the JWT token for platform users
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
isPlatformUser?: boolean;
|
||||
platformRole?: string;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the shape of the session object for platform users
|
||||
declare module "next-auth" {
|
||||
interface Session {
|
||||
user: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
image?: string;
|
||||
isPlatformUser?: boolean;
|
||||
platformRole?: string;
|
||||
companyId?: string;
|
||||
role?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
isPlatformUser?: boolean;
|
||||
platformRole?: string;
|
||||
companyId?: string;
|
||||
role?: string;
|
||||
}
|
||||
}
|
||||
// Type augmentation moved to types/next-auth.d.ts
|
||||
|
||||
export const platformAuthOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Enhanced Prisma client setup with connection pooling
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { createEnhancedPrismaClient } from "./database-pool.js";
|
||||
import { env } from "./env.js";
|
||||
import { createEnhancedPrismaClient } from "./database-pool";
|
||||
import { env } from "./env";
|
||||
|
||||
// Add prisma to the NodeJS global type
|
||||
declare const global: {
|
||||
|
||||
@@ -7,14 +7,14 @@ import {
|
||||
} from "@prisma/client";
|
||||
import cron from "node-cron";
|
||||
import fetch from "node-fetch";
|
||||
import { withRetry } from "./database-retry.js";
|
||||
import { prisma } from "./prisma.js";
|
||||
import { withRetry } from "./database-retry";
|
||||
import { prisma } from "./prisma";
|
||||
import {
|
||||
completeStage,
|
||||
failStage,
|
||||
getSessionsNeedingProcessing,
|
||||
startStage,
|
||||
} from "./processingStatusManager.js";
|
||||
} from "./processingStatusManager";
|
||||
import { getSchedulerConfig } from "./schedulerConfig";
|
||||
|
||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
@@ -137,14 +137,18 @@ interface ProcessingResult {
|
||||
|
||||
interface SessionMessage {
|
||||
id: string;
|
||||
timestamp: Date;
|
||||
sessionId: string;
|
||||
timestamp: Date | null;
|
||||
role: string;
|
||||
content: string;
|
||||
order: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface SessionForProcessing {
|
||||
id: string;
|
||||
companyId: string;
|
||||
endTime: Date;
|
||||
messages: SessionMessage[];
|
||||
}
|
||||
|
||||
@@ -250,7 +254,9 @@ async function processQuestions(
|
||||
});
|
||||
|
||||
// Filter and prepare unique questions
|
||||
const uniqueQuestions = [...new Set(questions.filter((q) => q.trim()))];
|
||||
const uniqueQuestions = Array.from(
|
||||
new Set(questions.filter((q) => q.trim()))
|
||||
);
|
||||
if (uniqueQuestions.length === 0) return;
|
||||
|
||||
// Batch create questions (skip duplicates)
|
||||
@@ -527,7 +533,7 @@ async function processSingleSession(
|
||||
const transcript = session.messages
|
||||
.map(
|
||||
(msg: SessionMessage) =>
|
||||
`[${new Date(msg.timestamp)
|
||||
`[${new Date(msg.timestamp ?? msg.createdAt)
|
||||
.toLocaleString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
@@ -710,9 +716,8 @@ async function processUnprocessedSessionsInternal(
|
||||
|
||||
// Filter to only sessions that have messages
|
||||
const sessionsWithMessages = sessionsToProcess.filter(
|
||||
(session): session is SessionForProcessing =>
|
||||
session.messages && session.messages.length > 0
|
||||
);
|
||||
(session) => session.messages && session.messages.length > 0
|
||||
) as SessionForProcessing[];
|
||||
|
||||
if (sessionsWithMessages.length === 0) {
|
||||
process.stdout.write(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ProcessingStage, ProcessingStatus } from "@prisma/client";
|
||||
import { prisma } from "./prisma.js";
|
||||
import { Prisma, ProcessingStage, ProcessingStatus } from "@prisma/client";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
// Type-safe metadata interfaces
|
||||
interface ProcessingMetadata {
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
}
|
||||
type ProcessingMetadata = Prisma.InputJsonObject;
|
||||
|
||||
interface WhereClause {
|
||||
status: ProcessingStatus;
|
||||
@@ -50,14 +48,14 @@ export async function startStage(
|
||||
status: ProcessingStatus.IN_PROGRESS,
|
||||
startedAt: new Date(),
|
||||
errorMessage: null,
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
create: {
|
||||
sessionId,
|
||||
stage,
|
||||
status: ProcessingStatus.IN_PROGRESS,
|
||||
startedAt: new Date(),
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -78,7 +76,7 @@ export async function completeStage(
|
||||
status: ProcessingStatus.COMPLETED,
|
||||
completedAt: new Date(),
|
||||
errorMessage: null,
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
create: {
|
||||
sessionId,
|
||||
@@ -86,7 +84,7 @@ export async function completeStage(
|
||||
status: ProcessingStatus.COMPLETED,
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -109,7 +107,7 @@ export async function failStage(
|
||||
completedAt: new Date(),
|
||||
errorMessage,
|
||||
retryCount: { increment: 1 },
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
create: {
|
||||
sessionId,
|
||||
@@ -119,7 +117,7 @@ export async function failStage(
|
||||
completedAt: new Date(),
|
||||
errorMessage,
|
||||
retryCount: 1,
|
||||
metadata: metadata || null,
|
||||
metadata: metadata ?? Prisma.DbNull,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Combined scheduler initialization with graceful shutdown
|
||||
|
||||
import { prisma } from "./prisma.js";
|
||||
import { prisma } from "./prisma";
|
||||
import { startProcessingScheduler } from "./processingScheduler";
|
||||
import { startCsvImportScheduler } from "./scheduler";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Transcript parsing utility for converting raw transcript content into structured messages
|
||||
import { prisma } from "./prisma.js";
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
export interface ParsedMessage {
|
||||
sessionId: string;
|
||||
@@ -156,7 +156,7 @@ export function parseTranscriptToMessages(
|
||||
}
|
||||
|
||||
// Calculate timestamps - use parsed timestamps if available, otherwise distribute across session duration
|
||||
interface MessageWithTimestamp extends ParsedMessage {
|
||||
interface MessageWithTimestamp extends Omit<ParsedMessage, "timestamp"> {
|
||||
timestamp: Date | string;
|
||||
}
|
||||
const hasTimestamps = messages.some(
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { UserRole } from "@prisma/client";
|
||||
import type { Session as NextAuthSession } from "next-auth";
|
||||
|
||||
export interface UserSession extends NextAuthSession {
|
||||
export interface UserSession extends Omit<NextAuthSession, "user"> {
|
||||
user: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
name?: string | null;
|
||||
email?: string;
|
||||
image?: string;
|
||||
image?: string | null;
|
||||
companyId: string;
|
||||
role: string;
|
||||
role: UserRole;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user