refactor: fix biome linting issues and update project documentation

- Fix 36+ biome linting issues reducing errors/warnings from 227 to 191
- Replace explicit 'any' types with proper TypeScript interfaces
- Fix React hooks dependencies and useCallback patterns
- Resolve unused variables and parameter assignment issues
- Improve accessibility with proper label associations
- Add comprehensive API documentation for admin and security features
- Update README.md with accurate PostgreSQL setup and current tech stack
- Create complete documentation for audit logging, CSP monitoring, and batch processing
- Fix outdated project information and missing developer workflows
This commit is contained in:
2025-07-11 21:50:53 +02:00
committed by Kaj Kowalski
parent 3e9e75e854
commit 1eea2cc3e4
121 changed files with 28687 additions and 4895 deletions

View File

@ -44,24 +44,83 @@ export class PreDeploymentChecker {
const startTime = Date.now();
try {
migrationLogger.startPhase("PRE_DEPLOYMENT", "Running pre-deployment validation checks");
migrationLogger.startPhase(
"PRE_DEPLOYMENT",
"Running pre-deployment validation checks"
);
// Define all checks to run
const checkSuite = [
{ name: "Environment Configuration", fn: () => this.checkEnvironmentConfiguration(), critical: true },
{ name: "Database Connection", fn: () => this.checkDatabaseConnection(), critical: true },
{ name: "Database Schema", fn: () => this.checkDatabaseSchema(), critical: true },
{ name: "Database Data Integrity", fn: () => this.checkDataIntegrity(), critical: true },
{ name: "Dependencies", fn: () => this.checkDependencies(), critical: true },
{ name: "File System Permissions", fn: () => this.checkFileSystemPermissions(), critical: false },
{ name: "Port Availability", fn: () => this.checkPortAvailability(), critical: true },
{ name: "OpenAI API Access", fn: () => this.checkOpenAIAccess(), critical: true },
{ name: "tRPC Infrastructure", fn: () => this.checkTRPCInfrastructure(), critical: true },
{ name: "Batch Processing Readiness", fn: () => this.checkBatchProcessingReadiness(), critical: true },
{ name: "Security Configuration", fn: () => this.checkSecurityConfiguration(), critical: false },
{ name: "Performance Configuration", fn: () => this.checkPerformanceConfiguration(), critical: false },
{ name: "Backup Validation", fn: () => this.checkBackupValidation(), critical: false },
{ name: "Migration Rollback Readiness", fn: () => this.checkRollbackReadiness(), critical: false },
{
name: "Environment Configuration",
fn: () => this.checkEnvironmentConfiguration(),
critical: true,
},
{
name: "Database Connection",
fn: () => this.checkDatabaseConnection(),
critical: true,
},
{
name: "Database Schema",
fn: () => this.checkDatabaseSchema(),
critical: true,
},
{
name: "Database Data Integrity",
fn: () => this.checkDataIntegrity(),
critical: true,
},
{
name: "Dependencies",
fn: () => this.checkDependencies(),
critical: true,
},
{
name: "File System Permissions",
fn: () => this.checkFileSystemPermissions(),
critical: false,
},
{
name: "Port Availability",
fn: () => this.checkPortAvailability(),
critical: true,
},
{
name: "OpenAI API Access",
fn: () => this.checkOpenAIAccess(),
critical: true,
},
{
name: "tRPC Infrastructure",
fn: () => this.checkTRPCInfrastructure(),
critical: true,
},
{
name: "Batch Processing Readiness",
fn: () => this.checkBatchProcessingReadiness(),
critical: true,
},
{
name: "Security Configuration",
fn: () => this.checkSecurityConfiguration(),
critical: false,
},
{
name: "Performance Configuration",
fn: () => this.checkPerformanceConfiguration(),
critical: false,
},
{
name: "Backup Validation",
fn: () => this.checkBackupValidation(),
critical: false,
},
{
name: "Migration Rollback Readiness",
fn: () => this.checkRollbackReadiness(),
critical: false,
},
];
// Run all checks
@ -70,8 +129,13 @@ export class PreDeploymentChecker {
}
const totalDuration = Date.now() - startTime;
const criticalFailures = this.checks.filter(c => c.critical && !c.success).length;
const warningCount = this.checks.reduce((sum, c) => sum + c.warnings.length, 0);
const criticalFailures = this.checks.filter(
(c) => c.critical && !c.success
).length;
const warningCount = this.checks.reduce(
(sum, c) => sum + c.warnings.length,
0
);
const result: PreDeploymentResult = {
success: criticalFailures === 0,
@ -84,13 +148,19 @@ export class PreDeploymentChecker {
if (result.success) {
migrationLogger.completePhase("PRE_DEPLOYMENT");
} else {
migrationLogger.error("PRE_DEPLOYMENT", `Pre-deployment checks failed with ${criticalFailures} critical failures`);
migrationLogger.error(
"PRE_DEPLOYMENT",
`Pre-deployment checks failed with ${criticalFailures} critical failures`
);
}
return result;
} catch (error) {
migrationLogger.error("PRE_DEPLOYMENT", "Pre-deployment check suite failed", error as Error);
migrationLogger.error(
"PRE_DEPLOYMENT",
"Pre-deployment check suite failed",
error as Error
);
throw error;
} finally {
await this.prisma.$disconnect();
@ -99,7 +169,7 @@ export class PreDeploymentChecker {
private async runSingleCheck(
name: string,
checkFn: () => Promise<Omit<CheckResult, 'name' | 'duration'>>,
checkFn: () => Promise<Omit<CheckResult, "name" | "duration">>,
critical: boolean
): Promise<void> {
const startTime = Date.now();
@ -120,20 +190,29 @@ export class PreDeploymentChecker {
this.checks.push(checkResult);
if (result.success) {
migrationLogger.info("CHECK", `${name} passed`, { duration, warnings: result.warnings.length });
migrationLogger.info("CHECK", `${name} passed`, {
duration,
warnings: result.warnings.length,
});
} else {
const level = critical ? "ERROR" : "WARN";
migrationLogger[level.toLowerCase() as 'error' | 'warn']("CHECK", `${name} failed`, undefined, {
errors: result.errors.length,
warnings: result.warnings.length,
duration
});
migrationLogger[level.toLowerCase() as "error" | "warn"](
"CHECK",
`${name} failed`,
undefined,
{
errors: result.errors.length,
warnings: result.warnings.length,
duration,
}
);
}
if (result.warnings.length > 0) {
migrationLogger.warn("CHECK", `${name} has warnings`, { warnings: result.warnings });
migrationLogger.warn("CHECK", `${name} has warnings`, {
warnings: result.warnings,
});
}
} catch (error) {
const duration = Date.now() - startTime;
const checkResult: CheckResult = {
@ -146,11 +225,15 @@ export class PreDeploymentChecker {
};
this.checks.push(checkResult);
migrationLogger.error("CHECK", `💥 ${name} crashed`, error as Error, { duration });
migrationLogger.error("CHECK", `💥 ${name} crashed`, error as Error, {
duration,
});
}
}
private async checkEnvironmentConfiguration(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkEnvironmentConfiguration(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -163,9 +246,9 @@ export class PreDeploymentChecker {
// Additional environment checks
const requiredVars = [
'DATABASE_URL',
'NEXTAUTH_SECRET',
'OPENAI_API_KEY'
"DATABASE_URL",
"NEXTAUTH_SECRET",
"OPENAI_API_KEY",
];
for (const varName of requiredVars) {
@ -175,17 +258,13 @@ export class PreDeploymentChecker {
}
// Check new variables
const newVars = [
'BATCH_PROCESSING_ENABLED',
'TRPC_ENDPOINT_URL'
];
const newVars = ["BATCH_PROCESSING_ENABLED", "TRPC_ENDPOINT_URL"];
for (const varName of newVars) {
if (!process.env[varName]) {
warnings.push(`New environment variable not set: ${varName}`);
}
}
} catch (error) {
errors.push(`Environment validation failed: ${(error as Error).message}`);
}
@ -197,7 +276,9 @@ export class PreDeploymentChecker {
};
}
private async checkDatabaseConnection(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkDatabaseConnection(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -215,7 +296,6 @@ export class PreDeploymentChecker {
if (connections.length !== 3) {
warnings.push("Connection pooling may have issues");
}
} catch (error) {
errors.push(`Database connection failed: ${(error as Error).message}`);
}
@ -227,7 +307,9 @@ export class PreDeploymentChecker {
};
}
private async checkDatabaseSchema(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkDatabaseSchema(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const validator = new DatabaseValidator();
try {
@ -247,7 +329,9 @@ export class PreDeploymentChecker {
}
}
private async checkDataIntegrity(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkDataIntegrity(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -257,11 +341,13 @@ export class PreDeploymentChecker {
const importCount = await this.prisma.sessionImport.count();
if (sessionCount === 0 && importCount === 0) {
warnings.push("No session data found - this may be a fresh installation");
warnings.push(
"No session data found - this may be a fresh installation"
);
}
// Check for orphaned processing status records
const orphanedStatus = await this.prisma.$queryRaw<{count: bigint}[]>`
const orphanedStatus = await this.prisma.$queryRaw<{ count: bigint }[]>`
SELECT COUNT(*) as count
FROM "SessionProcessingStatus" sps
LEFT JOIN "Session" s ON sps."sessionId" = s.id
@ -269,9 +355,10 @@ export class PreDeploymentChecker {
`;
if (orphanedStatus[0]?.count > 0) {
warnings.push(`Found ${orphanedStatus[0].count} orphaned processing status records`);
warnings.push(
`Found ${orphanedStatus[0].count} orphaned processing status records`
);
}
} catch (error) {
errors.push(`Data integrity check failed: ${(error as Error).message}`);
}
@ -283,7 +370,9 @@ export class PreDeploymentChecker {
};
}
private async checkDependencies(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkDependencies(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -307,19 +396,21 @@ export class PreDeploymentChecker {
];
for (const dep of requiredDeps) {
if (!packageJson.dependencies?.[dep] && !packageJson.devDependencies?.[dep]) {
if (
!packageJson.dependencies?.[dep] &&
!packageJson.devDependencies?.[dep]
) {
errors.push(`Missing required dependency: ${dep}`);
}
}
// Check Node.js version
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0]);
if (majorVersion < 18) {
errors.push(`Node.js ${nodeVersion} is too old. Requires Node.js 18+`);
}
} catch (error) {
errors.push(`Dependency check failed: ${(error as Error).message}`);
}
@ -331,7 +422,9 @@ export class PreDeploymentChecker {
};
}
private async checkFileSystemPermissions(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkFileSystemPermissions(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -346,7 +439,9 @@ export class PreDeploymentChecker {
await fs.writeFile(testFile, "test");
await fs.unlink(testFile);
} catch (error) {
errors.push(`Cannot write to logs directory: ${(error as Error).message}`);
errors.push(
`Cannot write to logs directory: ${(error as Error).message}`
);
}
// Check if we can write to backups directory
@ -357,11 +452,14 @@ export class PreDeploymentChecker {
await fs.writeFile(testFile, "test");
await fs.unlink(testFile);
} catch (error) {
warnings.push(`Cannot write to backups directory: ${(error as Error).message}`);
warnings.push(
`Cannot write to backups directory: ${(error as Error).message}`
);
}
} catch (error) {
errors.push(`File system permission check failed: ${(error as Error).message}`);
errors.push(
`File system permission check failed: ${(error as Error).message}`
);
}
return {
@ -371,7 +469,9 @@ export class PreDeploymentChecker {
};
}
private async checkPortAvailability(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkPortAvailability(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -396,9 +496,10 @@ export class PreDeploymentChecker {
resolve();
});
});
} catch (error) {
errors.push(`Port availability check failed: ${(error as Error).message}`);
errors.push(
`Port availability check failed: ${(error as Error).message}`
);
}
return {
@ -408,7 +509,9 @@ export class PreDeploymentChecker {
};
}
private async checkOpenAIAccess(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkOpenAIAccess(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -423,19 +526,20 @@ export class PreDeploymentChecker {
// Test API access (simple models list call)
const response = await fetch("https://api.openai.com/v1/models", {
headers: {
"Authorization": `Bearer ${apiKey}`,
Authorization: `Bearer ${apiKey}`,
},
});
if (!response.ok) {
errors.push(`OpenAI API access failed: ${response.status} ${response.statusText}`);
errors.push(
`OpenAI API access failed: ${response.status} ${response.statusText}`
);
} else {
const data = await response.json();
if (!data.data || !Array.isArray(data.data)) {
warnings.push("OpenAI API returned unexpected response format");
}
}
} catch (error) {
errors.push(`OpenAI API check failed: ${(error as Error).message}`);
}
@ -447,7 +551,9 @@ export class PreDeploymentChecker {
};
}
private async checkTRPCInfrastructure(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkTRPCInfrastructure(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -475,9 +581,10 @@ export class PreDeploymentChecker {
} catch (error) {
errors.push(`Cannot import tRPC router: ${(error as Error).message}`);
}
} catch (error) {
errors.push(`tRPC infrastructure check failed: ${(error as Error).message}`);
errors.push(
`tRPC infrastructure check failed: ${(error as Error).message}`
);
}
return {
@ -487,16 +594,15 @@ export class PreDeploymentChecker {
};
}
private async checkBatchProcessingReadiness(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkBatchProcessingReadiness(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
try {
// Check if batch processing files exist
const batchFiles = [
"lib/batchProcessor.ts",
"lib/batchScheduler.ts",
];
const batchFiles = ["lib/batchProcessor.ts", "lib/batchScheduler.ts"];
for (const file of batchFiles) {
const fullPath = join(process.cwd(), file);
@ -506,29 +612,32 @@ export class PreDeploymentChecker {
}
// Check database readiness for batch processing
const batchTableExists = await this.prisma.$queryRaw<{count: string}[]>`
const batchTableExists = await this.prisma.$queryRaw<{ count: string }[]>`
SELECT COUNT(*) as count
FROM information_schema.tables
WHERE table_name = 'AIBatchRequest'
`;
if (parseInt(batchTableExists[0]?.count || '0') === 0) {
if (parseInt(batchTableExists[0]?.count || "0") === 0) {
errors.push("AIBatchRequest table not found");
}
// Check if batch status enum exists
const batchStatusExists = await this.prisma.$queryRaw<{count: string}[]>`
const batchStatusExists = await this.prisma.$queryRaw<
{ count: string }[]
>`
SELECT COUNT(*) as count
FROM pg_type
WHERE typname = 'AIBatchRequestStatus'
`;
if (parseInt(batchStatusExists[0]?.count || '0') === 0) {
if (parseInt(batchStatusExists[0]?.count || "0") === 0) {
errors.push("AIBatchRequestStatus enum not found");
}
} catch (error) {
errors.push(`Batch processing readiness check failed: ${(error as Error).message}`);
errors.push(
`Batch processing readiness check failed: ${(error as Error).message}`
);
}
return {
@ -538,7 +647,9 @@ export class PreDeploymentChecker {
};
}
private async checkSecurityConfiguration(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkSecurityConfiguration(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -556,13 +667,17 @@ export class PreDeploymentChecker {
// Check if we're running in production mode with proper settings
if (process.env.NODE_ENV === "production") {
if (!process.env.NEXTAUTH_URL || process.env.NEXTAUTH_URL.includes("localhost")) {
if (
!process.env.NEXTAUTH_URL ||
process.env.NEXTAUTH_URL.includes("localhost")
) {
warnings.push("NEXTAUTH_URL should not use localhost in production");
}
}
} catch (error) {
warnings.push(`Security configuration check failed: ${(error as Error).message}`);
warnings.push(
`Security configuration check failed: ${(error as Error).message}`
);
}
return {
@ -572,31 +687,44 @@ export class PreDeploymentChecker {
};
}
private async checkPerformanceConfiguration(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkPerformanceConfiguration(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
try {
// Check database connection limits
const connectionLimit = parseInt(process.env.DATABASE_CONNECTION_LIMIT || "20");
const connectionLimit = parseInt(
process.env.DATABASE_CONNECTION_LIMIT || "20"
);
if (connectionLimit < 10) {
warnings.push("DATABASE_CONNECTION_LIMIT may be too low for production");
warnings.push(
"DATABASE_CONNECTION_LIMIT may be too low for production"
);
}
// Check batch processing configuration
const batchMaxRequests = parseInt(process.env.BATCH_MAX_REQUESTS || "1000");
const batchMaxRequests = parseInt(
process.env.BATCH_MAX_REQUESTS || "1000"
);
if (batchMaxRequests > 50000) {
warnings.push("BATCH_MAX_REQUESTS exceeds OpenAI limits");
}
// Check session processing concurrency
const concurrency = parseInt(process.env.SESSION_PROCESSING_CONCURRENCY || "5");
const concurrency = parseInt(
process.env.SESSION_PROCESSING_CONCURRENCY || "5"
);
if (concurrency > 10) {
warnings.push("High SESSION_PROCESSING_CONCURRENCY may overwhelm the system");
warnings.push(
"High SESSION_PROCESSING_CONCURRENCY may overwhelm the system"
);
}
} catch (error) {
warnings.push(`Performance configuration check failed: ${(error as Error).message}`);
warnings.push(
`Performance configuration check failed: ${(error as Error).message}`
);
}
return {
@ -606,7 +734,9 @@ export class PreDeploymentChecker {
};
}
private async checkBackupValidation(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkBackupValidation(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -625,7 +755,6 @@ export class PreDeploymentChecker {
if (!existsSync(backupDir)) {
warnings.push("Backup directory does not exist");
}
} catch (error) {
warnings.push(`Backup validation failed: ${(error as Error).message}`);
}
@ -637,7 +766,9 @@ export class PreDeploymentChecker {
};
}
private async checkRollbackReadiness(): Promise<Omit<CheckResult, 'name' | 'duration'>> {
private async checkRollbackReadiness(): Promise<
Omit<CheckResult, "name" | "duration">
> {
const errors: string[] = [];
const warnings: string[] = [];
@ -659,9 +790,10 @@ export class PreDeploymentChecker {
if (process.env.MIGRATION_ROLLBACK_ENABLED !== "true") {
warnings.push("Rollback is disabled - consider enabling for safety");
}
} catch (error) {
warnings.push(`Rollback readiness check failed: ${(error as Error).message}`);
warnings.push(
`Rollback readiness check failed: ${(error as Error).message}`
);
}
return {
@ -676,41 +808,46 @@ export class PreDeploymentChecker {
if (import.meta.url === `file://${process.argv[1]}`) {
const checker = new PreDeploymentChecker();
checker.runAllChecks()
checker
.runAllChecks()
.then((result) => {
console.log('\n=== PRE-DEPLOYMENT CHECK RESULTS ===');
console.log(`Overall Success: ${result.success ? '✅' : '❌'}`);
console.log("\n=== PRE-DEPLOYMENT CHECK RESULTS ===");
console.log(`Overall Success: ${result.success ? "✅" : "❌"}`);
console.log(`Total Duration: ${result.totalDuration}ms`);
console.log(`Critical Failures: ${result.criticalFailures}`);
console.log(`Total Warnings: ${result.warningCount}`);
console.log('\n=== INDIVIDUAL CHECKS ===');
console.log("\n=== INDIVIDUAL CHECKS ===");
for (const check of result.checks) {
const status = check.success ? '✅' : '❌';
const critical = check.critical ? ' (CRITICAL)' : '';
const status = check.success ? "✅" : "❌";
const critical = check.critical ? " (CRITICAL)" : "";
console.log(`${status} ${check.name}${critical} (${check.duration}ms)`);
if (check.errors.length > 0) {
check.errors.forEach(error => console.log(`${error}`));
check.errors.forEach((error) => console.log(`${error}`));
}
if (check.warnings.length > 0) {
check.warnings.forEach(warning => console.log(` ⚠️ ${warning}`));
check.warnings.forEach((warning) => console.log(` ⚠️ ${warning}`));
}
}
if (!result.success) {
console.log('\n❌ DEPLOYMENT BLOCKED - Fix critical issues before proceeding');
console.log(
"\n❌ DEPLOYMENT BLOCKED - Fix critical issues before proceeding"
);
} else if (result.warningCount > 0) {
console.log('\n⚠ DEPLOYMENT ALLOWED - Review warnings before proceeding');
console.log(
"\n⚠ DEPLOYMENT ALLOWED - Review warnings before proceeding"
);
} else {
console.log('\n✅ DEPLOYMENT READY - All checks passed');
console.log("\n✅ DEPLOYMENT READY - All checks passed");
}
process.exit(result.success ? 0 : 1);
})
.catch((error) => {
console.error('Pre-deployment checks failed:', error);
console.error("Pre-deployment checks failed:", error);
process.exit(1);
});
}
}