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:
2026-01-20 07:28:10 +01:00
parent 8b3846539f
commit 5bfd762e55
161 changed files with 14655 additions and 11682 deletions

View File

@@ -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) {

View File

@@ -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 = () => {

View File

@@ -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,
}),

View File

@@ -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);

View File

@@ -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: [

View File

@@ -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: {

View File

@@ -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(

View File

@@ -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,
},
});
}

View File

@@ -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";

View File

@@ -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(

View File

@@ -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;
};
}