feat: complete development environment setup and code quality improvements

- Set up pre-commit hooks with husky and lint-staged for automated code quality
- Improved TypeScript type safety by replacing 'any' types with proper generics
- Fixed markdown linting violations (MD030 spacing) across all documentation
- Fixed compound adjective hyphenation in technical documentation
- Fixed invalid JSON union syntax in API documentation examples
- Automated code formatting and linting on commit
- Enhanced error handling with better type constraints
- Configured biome and markdownlint for consistent code style
- All changes verified with successful production build
This commit is contained in:
2025-07-13 14:44:05 +02:00
parent 1d4e695e41
commit e2301725a3
54 changed files with 2335 additions and 1863 deletions

View File

@@ -156,9 +156,12 @@ async function getPerformanceHistory(limit: number) {
0 0
) / history.length ) / history.length
: 0, : 0,
memoryTrend: calculateTrend(history, "memoryUsage.heapUsed"), memoryTrend: calculateTrend(
history as unknown as Record<string, unknown>[],
"memoryUsage.heapUsed"
),
responseTrend: calculateTrend( responseTrend: calculateTrend(
history, history as unknown as Record<string, unknown>[],
"requestMetrics.averageResponseTime" "requestMetrics.averageResponseTime"
), ),
}, },
@@ -539,8 +542,8 @@ function _calculateAverage(
: 0; : 0;
} }
function calculateTrend( function calculateTrend<T extends Record<string, unknown>>(
history: Array<any>, history: Array<T>,
path: string path: string
): "increasing" | "decreasing" | "stable" { ): "increasing" | "decreasing" | "stable" {
if (history.length < 2) return "stable"; if (history.length < 2) return "stable";
@@ -570,10 +573,18 @@ function calculateTrend(
return "stable"; return "stable";
} }
function getNestedPropertyValue(obj: any, path: string): number { function getNestedPropertyValue(
return ( obj: Record<string, unknown>,
path.split(".").reduce((current, key) => current?.[key] ?? 0, obj) || 0 path: string
); ): number {
const result = path.split(".").reduce((current, key) => {
if (current && typeof current === "object" && key in current) {
return (current as Record<string, unknown>)[key];
}
return 0;
}, obj as unknown);
return typeof result === "number" ? result : 0;
} }
function getNestedValue(obj: Record<string, unknown>, path: string): unknown { function getNestedValue(obj: Record<string, unknown>, path: string): unknown {

View File

@@ -1,6 +1,6 @@
import { getSchedulerIntegration } from "@/lib/services/schedulers/ServerSchedulerIntegration";
import { createAdminHandler } from "@/lib/api";
import { z } from "zod"; import { z } from "zod";
import { createAdminHandler } from "@/lib/api";
import { getSchedulerIntegration } from "@/lib/services/schedulers/ServerSchedulerIntegration";
/** /**
* Get all schedulers with their status and metrics * Get all schedulers with their status and metrics
@@ -21,10 +21,12 @@ export const GET = createAdminHandler(async (_context) => {
}; };
}); });
const PostInputSchema = z.object({ const PostInputSchema = z
.object({
action: z.enum(["start", "stop", "trigger", "startAll", "stopAll"]), action: z.enum(["start", "stop", "trigger", "startAll", "stopAll"]),
schedulerId: z.string().optional(), schedulerId: z.string().optional(),
}).refine( })
.refine(
(data) => { (data) => {
// schedulerId is required for individual scheduler actions // schedulerId is required for individual scheduler actions
const actionsRequiringSchedulerId = ["start", "stop", "trigger"]; const actionsRequiringSchedulerId = ["start", "stop", "trigger"];
@@ -37,14 +39,17 @@ const PostInputSchema = z.object({
message: "schedulerId is required for start, stop, and trigger actions", message: "schedulerId is required for start, stop, and trigger actions",
path: ["schedulerId"], path: ["schedulerId"],
} }
); );
/** /**
* Control scheduler operations (start/stop/trigger) * Control scheduler operations (start/stop/trigger)
* Requires admin authentication * Requires admin authentication
*/ */
export const POST = createAdminHandler(async (_context, validatedData) => { export const POST = createAdminHandler(
const { action, schedulerId } = validatedData; async (_context, validatedData) => {
const { action, schedulerId } = validatedData as z.infer<
typeof PostInputSchema
>;
const integration = getSchedulerIntegration(); const integration = getSchedulerIntegration();
@@ -87,6 +92,8 @@ export const POST = createAdminHandler(async (_context, validatedData) => {
message: `Action '${action}' completed successfully`, message: `Action '${action}' completed successfully`,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
}, { },
{
validateInput: PostInputSchema, validateInput: PostInputSchema,
}); }
);

View File

@@ -7,20 +7,20 @@
import { type NextRequest, NextResponse } from "next/server"; import { type NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { authOptions } from "../../../../lib/auth"; import { withErrorHandling } from "@/lib/api/errors";
import { sessionMetrics } from "../../../../lib/metrics"; import { createSuccessResponse } from "@/lib/api/response";
import { prisma } from "../../../../lib/prisma"; import { caches } from "@/lib/performance/cache";
import type { ChatSession } from "../../../../lib/types"; import { deduplicators } from "@/lib/performance/deduplication";
// Performance system imports // Performance system imports
import { import {
PerformanceUtils, PerformanceUtils,
performanceMonitor, performanceMonitor,
} from "@/lib/performance/monitor"; } from "@/lib/performance/monitor";
import { caches } from "@/lib/performance/cache"; import { authOptions } from "../../../../lib/auth";
import { deduplicators } from "@/lib/performance/deduplication"; import { sessionMetrics } from "../../../../lib/metrics";
import { withErrorHandling } from "@/lib/api/errors"; import { prisma } from "../../../../lib/prisma";
import { createSuccessResponse } from "@/lib/api/response"; import type { ChatSession, MetricsResult } from "../../../../lib/types";
/** /**
* Converts a Prisma session to ChatSession format for metrics * Converts a Prisma session to ChatSession format for metrics
@@ -101,9 +101,14 @@ interface MetricsRequestParams {
} }
interface MetricsResponse { interface MetricsResponse {
metrics: any; metrics: MetricsResult;
csvUrl: string | null; csvUrl: string | null;
company: any; company: {
id: string;
name: string;
csvUrl: string;
status: string;
};
dateRange: { minDate: string; maxDate: string } | null; dateRange: { minDate: string; maxDate: string } | null;
performanceMetrics?: { performanceMetrics?: {
cacheHit: boolean; cacheHit: boolean;
@@ -207,9 +212,16 @@ const fetchQuestionsWithDeduplication = deduplicators.database.memoize(
*/ */
const calculateMetricsWithCache = async ( const calculateMetricsWithCache = async (
chatSessions: ChatSession[], chatSessions: ChatSession[],
companyConfig: any, companyConfig: Record<string, unknown>,
cacheKey: string cacheKey: string
): Promise<{ result: any; fromCache: boolean }> => { ): Promise<{
result: {
metrics: MetricsResult;
calculatedAt: string;
sessionCount: number;
};
fromCache: boolean;
}> => {
return caches.metrics return caches.metrics
.getOrCompute( .getOrCompute(
cacheKey, cacheKey,
@@ -326,7 +338,7 @@ export const GET = withErrorHandling(async (request: NextRequest) => {
deduplicationHit = deduplicators.database.getStats().hitRate > 0; deduplicationHit = deduplicators.database.getStats().hitRate > 0;
// Fetch questions with deduplication // Fetch questions with deduplication
const sessionIds = prismaSessions.map((s: any) => s.id); const sessionIds = prismaSessions.map((s) => s.id);
const questionsResult = await fetchQuestionsWithDeduplication(sessionIds); const questionsResult = await fetchQuestionsWithDeduplication(sessionIds);
const sessionQuestions = questionsResult.result; const sessionQuestions = questionsResult.result;
@@ -349,7 +361,7 @@ export const GET = withErrorHandling(async (request: NextRequest) => {
const { result: chatSessions } = await PerformanceUtils.measureAsync( const { result: chatSessions } = await PerformanceUtils.measureAsync(
"metrics-session-conversion", "metrics-session-conversion",
async () => { async () => {
return prismaSessions.map((ps: any) => { return prismaSessions.map((ps) => {
const questions = questionsBySession[ps.id] || []; const questions = questionsBySession[ps.id] || [];
return convertToMockChatSession(ps, questions); return convertToMockChatSession(ps, questions);
}); });
@@ -372,7 +384,7 @@ export const GET = withErrorHandling(async (request: NextRequest) => {
if (prismaSessions.length === 0) return null; if (prismaSessions.length === 0) return null;
const dates = prismaSessions const dates = prismaSessions
.map((s: any) => new Date(s.startTime)) .map((s) => new Date(s.startTime))
.sort((a: Date, b: Date) => a.getTime() - b.getTime()); .sort((a: Date, b: Date) => a.getTime() - b.getTime());
return { return {

View File

@@ -247,7 +247,8 @@ function useCompanyData(
}); });
} }
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
console.error("Failed to fetch company - Network/Parse Error:", { console.error("Failed to fetch company - Network/Parse Error:", {
message: errorMessage, message: errorMessage,
@@ -373,7 +374,8 @@ function renderCompanyInfoCard(
const parsedValue = Number.parseInt(value, 10); const parsedValue = Number.parseInt(value, 10);
// Validate input: must be a positive number // Validate input: must be a positive number
const maxUsers = !Number.isNaN(parsedValue) && parsedValue > 0 const maxUsers =
!Number.isNaN(parsedValue) && parsedValue > 0
? parsedValue ? parsedValue
: 1; // Default to 1 for invalid/negative values : 1; // Default to 1 for invalid/negative values

View File

@@ -156,7 +156,9 @@ function usePlatformDashboardState() {
adminPassword: "", adminPassword: "",
maxUsers: 10, maxUsers: 10,
}); });
const [validationErrors, setValidationErrors] = useState<ValidationErrors>({}); const [validationErrors, setValidationErrors] = useState<ValidationErrors>(
{}
);
return { return {
dashboardData, dashboardData,
@@ -211,7 +213,8 @@ function useFormIds() {
function validateEmail(email: string): string | undefined { function validateEmail(email: string): string | undefined {
if (!email) return undefined; if (!email) return undefined;
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; const emailRegex =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
if (!emailRegex.test(email)) { if (!emailRegex.test(email)) {
return "Please enter a valid email address"; return "Please enter a valid email address";
@@ -225,7 +228,7 @@ function validateUrl(url: string): string | undefined {
try { try {
const urlObj = new URL(url); const urlObj = new URL(url);
if (!['http:', 'https:'].includes(urlObj.protocol)) { if (!["http:", "https:"].includes(urlObj.protocol)) {
return "URL must use HTTP or HTTPS protocol"; return "URL must use HTTP or HTTPS protocol";
} }
return undefined; return undefined;
@@ -686,8 +689,7 @@ export default function PlatformDashboard() {
// Check for validation errors // Check for validation errors
const hasValidationErrors = !!( const hasValidationErrors = !!(
validationErrors.csvUrl || validationErrors.csvUrl || validationErrors.adminEmail
validationErrors.adminEmail
); );
return hasRequiredFields && !hasValidationErrors; return hasRequiredFields && !hasValidationErrors;

View File

@@ -71,8 +71,7 @@ export default function MessageViewer({ messages }: MessageViewerProps) {
: "No timestamp"} : "No timestamp"}
</span> </span>
<span> <span>
Last message:{" "} Last message: {(() => {
{(() => {
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
return lastMessage.timestamp return lastMessage.timestamp
? new Date(lastMessage.timestamp).toLocaleString() ? new Date(lastMessage.timestamp).toLocaleString()

View File

@@ -23,6 +23,7 @@ The core CSRF functionality includes:
- **Client Utilities**: Browser-side token management and request enhancement - **Client Utilities**: Browser-side token management and request enhancement
**Key Functions:** **Key Functions:**
- `generateCSRFToken()` - Creates new CSRF tokens - `generateCSRFToken()` - Creates new CSRF tokens
- `verifyCSRFToken()` - Validates tokens server-side - `verifyCSRFToken()` - Validates tokens server-side
- `CSRFProtection.validateRequest()` - Request validation middleware - `CSRFProtection.validateRequest()` - Request validation middleware
@@ -33,6 +34,7 @@ The core CSRF functionality includes:
Provides automatic CSRF protection for API endpoints: Provides automatic CSRF protection for API endpoints:
**Protected Endpoints:** **Protected Endpoints:**
- `/api/auth/*` - Authentication endpoints - `/api/auth/*` - Authentication endpoints
- `/api/register` - User registration - `/api/register` - User registration
- `/api/forgot-password` - Password reset requests - `/api/forgot-password` - Password reset requests
@@ -42,12 +44,14 @@ Provides automatic CSRF protection for API endpoints:
- `/api/trpc/*` - All tRPC endpoints - `/api/trpc/*` - All tRPC endpoints
**Protected Methods:** **Protected Methods:**
- `POST` - Create operations - `POST` - Create operations
- `PUT` - Update operations - `PUT` - Update operations
- `DELETE` - Delete operations - `DELETE` - Delete operations
- `PATCH` - Partial update operations - `PATCH` - Partial update operations
**Safe Methods (Not Protected):** **Safe Methods (Not Protected):**
- `GET` - Read operations - `GET` - Read operations
- `HEAD` - Metadata requests - `HEAD` - Metadata requests
- `OPTIONS` - CORS preflight requests - `OPTIONS` - CORS preflight requests
@@ -57,12 +61,14 @@ Provides automatic CSRF protection for API endpoints:
CSRF protection integrated into tRPC procedures: CSRF protection integrated into tRPC procedures:
**New Procedure Types:** **New Procedure Types:**
- `csrfProtectedProcedure` - Basic CSRF protection - `csrfProtectedProcedure` - Basic CSRF protection
- `csrfProtectedAuthProcedure` - CSRF + authentication protection - `csrfProtectedAuthProcedure` - CSRF + authentication protection
- `csrfProtectedCompanyProcedure` - CSRF + company access protection - `csrfProtectedCompanyProcedure` - CSRF + company access protection
- `csrfProtectedAdminProcedure` - CSRF + admin access protection - `csrfProtectedAdminProcedure` - CSRF + admin access protection
**Updated Router Example:** **Updated Router Example:**
```typescript ```typescript
// Before // Before
register: rateLimitedProcedure register: rateLimitedProcedure
@@ -78,20 +84,24 @@ register: csrfProtectedProcedure
### 4. Client-Side Integration ### 4. Client-Side Integration
#### tRPC Client (`lib/trpc-client.ts`) #### tRPC Client (`lib/trpc-client.ts`)
- Automatic CSRF token inclusion in tRPC requests - Automatic CSRF token inclusion in tRPC requests
- Token extracted from cookies and added to request headers - Token extracted from cookies and added to request headers
#### React Hooks (`lib/hooks/useCSRF.ts`) #### React Hooks (`lib/hooks/useCSRF.ts`)
- `useCSRF()` - Basic token management - `useCSRF()` - Basic token management
- `useCSRFFetch()` - Enhanced fetch with automatic CSRF tokens - `useCSRFFetch()` - Enhanced fetch with automatic CSRF tokens
- `useCSRFForm()` - Form submission with CSRF protection - `useCSRFForm()` - Form submission with CSRF protection
#### Provider Component (`components/providers/CSRFProvider.tsx`) #### Provider Component (`components/providers/CSRFProvider.tsx`)
- Application-wide CSRF token management - Application-wide CSRF token management
- Automatic token fetching and refresh - Automatic token fetching and refresh
- Context-based token sharing - Context-based token sharing
#### Protected Form Component (`components/forms/CSRFProtectedForm.tsx`) #### Protected Form Component (`components/forms/CSRFProtectedForm.tsx`)
- Ready-to-use form component with CSRF protection - Ready-to-use form component with CSRF protection
- Automatic token inclusion in form submissions - Automatic token inclusion in form submissions
- Graceful fallback for non-JavaScript environments - Graceful fallback for non-JavaScript environments
@@ -99,6 +109,7 @@ register: csrfProtectedProcedure
### 5. API Endpoint (`app/api/csrf-token/route.ts`) ### 5. API Endpoint (`app/api/csrf-token/route.ts`)
Provides CSRF tokens to client applications: Provides CSRF tokens to client applications:
- `GET /api/csrf-token` - Returns new CSRF token - `GET /api/csrf-token` - Returns new CSRF token
- Sets HTTP-only cookie for automatic inclusion - Sets HTTP-only cookie for automatic inclusion
- Used by client-side hooks and components - Used by client-side hooks and components
@@ -206,12 +217,14 @@ const dataWithToken = CSRFClient.addTokenToObject({ data: 'example' });
## Security Features ## Security Features
### 1. Token Properties ### 1. Token Properties
- **Cryptographically Secure**: Uses the `csrf` library with secure random generation - **Cryptographically Secure**: Uses the `csrf` library with secure random generation
- **Short-Lived**: 24-hour expiration by default - **Short-Lived**: 24-hour expiration by default
- **HTTP-Only Cookies**: Prevents XSS-based token theft - **HTTP-Only Cookies**: Prevents XSS-based token theft
- **SameSite Protection**: Reduces CSRF attack surface - **SameSite Protection**: Reduces CSRF attack surface
### 2. Validation Process ### 2. Validation Process
1. Extract token from request (header, form data, or JSON body) 1. Extract token from request (header, form data, or JSON body)
2. Retrieve stored token from HTTP-only cookie 2. Retrieve stored token from HTTP-only cookie
3. Verify tokens match 3. Verify tokens match
@@ -219,6 +232,7 @@ const dataWithToken = CSRFClient.addTokenToObject({ data: 'example' });
5. Allow or reject request based on validation 5. Allow or reject request based on validation
### 3. Error Handling ### 3. Error Handling
- **Graceful Degradation**: Form fallbacks for JavaScript-disabled browsers - **Graceful Degradation**: Form fallbacks for JavaScript-disabled browsers
- **Clear Error Messages**: Specific error codes for debugging - **Clear Error Messages**: Specific error codes for debugging
- **Rate Limiting Integration**: Works with existing auth rate limiting - **Rate Limiting Integration**: Works with existing auth rate limiting
@@ -227,12 +241,14 @@ const dataWithToken = CSRFClient.addTokenToObject({ data: 'example' });
## Testing ## Testing
### Test Coverage ### Test Coverage
- **Unit Tests**: Token generation, validation, and client utilities - **Unit Tests**: Token generation, validation, and client utilities
- **Integration Tests**: Middleware behavior and endpoint protection - **Integration Tests**: Middleware behavior and endpoint protection
- **Component Tests**: React hooks and form components - **Component Tests**: React hooks and form components
- **End-to-End**: Full request/response cycle testing - **End-to-End**: Full request/response cycle testing
### Running Tests ### Running Tests
```bash ```bash
# Run all CSRF tests # Run all CSRF tests
pnpm test:vitest tests/unit/csrf*.test.ts tests/integration/csrf*.test.ts pnpm test:vitest tests/unit/csrf*.test.ts tests/integration/csrf*.test.ts
@@ -246,7 +262,9 @@ pnpm test:vitest tests/unit/csrf-hooks.test.tsx
## Monitoring and Debugging ## Monitoring and Debugging
### CSRF Validation Logs ### CSRF Validation Logs
Failed CSRF validations are logged with details: Failed CSRF validations are logged with details:
``` ```
CSRF validation failed for POST /api/dashboard/sessions: CSRF token missing from request CSRF validation failed for POST /api/dashboard/sessions: CSRF token missing from request
``` ```
@@ -271,7 +289,9 @@ CSRF validation failed for POST /api/dashboard/sessions: CSRF token missing from
## Migration Guide ## Migration Guide
### For Existing Endpoints ### For Existing Endpoints
1. Update tRPC procedures to use CSRF-protected variants: 1. Update tRPC procedures to use CSRF-protected variants:
```typescript ```typescript
// Old // Old
someAction: protectedProcedure.mutation(...) someAction: protectedProcedure.mutation(...)
@@ -281,6 +301,7 @@ CSRF validation failed for POST /api/dashboard/sessions: CSRF token missing from
``` ```
2. Update client components to use CSRF hooks: 2. Update client components to use CSRF hooks:
```tsx ```tsx
// Old // Old
const { data, mutate } = trpc.user.update.useMutation(); const { data, mutate } = trpc.user.update.useMutation();
@@ -290,6 +311,7 @@ CSRF validation failed for POST /api/dashboard/sessions: CSRF token missing from
``` ```
3. Update manual API calls to include CSRF tokens: 3. Update manual API calls to include CSRF tokens:
```typescript ```typescript
// Old // Old
fetch('/api/endpoint', { method: 'POST', ... }); fetch('/api/endpoint', { method: 'POST', ... });

View File

@@ -10,7 +10,7 @@ The Admin Audit Logs API provides secure access to security audit trails for adm
- **Authentication**: NextAuth.js session required - **Authentication**: NextAuth.js session required
- **Authorization**: ADMIN role required for all endpoints - **Authorization**: ADMIN role required for all endpoints
- **Rate Limiting**: Integrated with existing auth rate limiting system - **Rate-Limiting**: Integrated with existing authentication rate-limiting system
- **Audit Trail**: All API access is logged for security monitoring - **Audit Trail**: All API access is logged for security monitoring
## API Endpoints ## API Endpoints
@@ -128,12 +128,14 @@ POST /api/admin/audit-logs/retention
```json ```json
{ {
"action": "cleanup" | "configure" | "status", "action": "cleanup",
"retentionDays": 90, "retentionDays": 90,
"dryRun": true "dryRun": true
} }
``` ```
**Note**: `action` field accepts one of: `"cleanup"`, `"configure"`, or `"status"`
#### Parameters #### Parameters
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
@@ -145,6 +147,7 @@ POST /api/admin/audit-logs/retention
#### Example Requests #### Example Requests
**Check retention status:** **Check retention status:**
```javascript ```javascript
const response = await fetch('/api/admin/audit-logs/retention', { const response = await fetch('/api/admin/audit-logs/retention', {
method: 'POST', method: 'POST',
@@ -154,6 +157,7 @@ const response = await fetch('/api/admin/audit-logs/retention', {
``` ```
**Configure retention policy:** **Configure retention policy:**
```javascript ```javascript
const response = await fetch('/api/admin/audit-logs/retention', { const response = await fetch('/api/admin/audit-logs/retention', {
method: 'POST', method: 'POST',
@@ -166,6 +170,7 @@ const response = await fetch('/api/admin/audit-logs/retention', {
``` ```
**Cleanup old logs (dry run):** **Cleanup old logs (dry run):**
```javascript ```javascript
const response = await fetch('/api/admin/audit-logs/retention', { const response = await fetch('/api/admin/audit-logs/retention', {
method: 'POST', method: 'POST',
@@ -180,17 +185,20 @@ const response = await fetch('/api/admin/audit-logs/retention', {
## Security Features ## Security Features
### Access Control ### Access Control
- **Role-based Access**: Only ADMIN users can access audit logs - **Role-based Access**: Only ADMIN users can access audit logs
- **Company Isolation**: Users only see logs for their company - **Company Isolation**: Users only see logs for their company
- **Session Validation**: Active NextAuth session required - **Session Validation**: Active NextAuth session required
### Audit Trail ### Audit Trail
- **Access Logging**: All audit log access is recorded - **Access Logging**: All audit log access is recorded
- **Metadata Tracking**: Request parameters and results are logged - **Metadata Tracking**: Request parameters and results are logged
- **IP Tracking**: Client IP addresses are recorded for all requests - **IP Tracking**: Client IP addresses are recorded for all requests
### Rate Limiting ### Rate Limiting
- **Integrated Protection**: Uses existing authentication rate limiting
- **Integrated Protection**: Uses existing authentication rate-limiting
- **Abuse Prevention**: Protects against excessive API usage - **Abuse Prevention**: Protects against excessive API usage
- **Error Tracking**: Failed attempts are monitored - **Error Tracking**: Failed attempts are monitored
@@ -294,16 +302,19 @@ async function getUserActivity(userId, days = 7) {
## Performance Considerations ## Performance Considerations
### Database Optimization ### Database Optimization
- **Indexed Queries**: All filter columns are properly indexed - **Indexed Queries**: All filter columns are properly indexed
- **Pagination**: Efficient offset-based pagination with limits - **Pagination**: Efficient offset-based pagination with limits
- **Time Range Filtering**: Optimized for date range queries - **Time Range Filtering**: Optimized for date range queries
### Memory Usage ### Memory Usage
- **Limited Results**: Maximum 100 records per request - **Limited Results**: Maximum 100 records per request
- **Streaming**: Large exports use streaming for memory efficiency - **Streaming**: Large exports use streaming for memory efficiency
- **Connection Pooling**: Database connections are pooled - **Connection Pooling**: Database connections are pooled
### Caching Considerations ### Caching Considerations
- **No Caching**: Audit logs are never cached for security reasons - **No Caching**: Audit logs are never cached for security reasons
- **Fresh Data**: All queries hit the database for real-time results - **Fresh Data**: All queries hit the database for real-time results
- **Read Replicas**: Consider using read replicas for heavy reporting - **Read Replicas**: Consider using read replicas for heavy reporting
@@ -335,7 +346,7 @@ try {
} }
``` ```
### Rate Limiting Handling ### Rate-Limiting Handling
```javascript ```javascript
async function fetchWithRetry(url, options = {}) { async function fetchWithRetry(url, options = {}) {
@@ -354,32 +365,37 @@ async function fetchWithRetry(url, options = {}) {
## Monitoring and Alerting ## Monitoring and Alerting
### Key Metrics to Monitor ### Key Metrics to Monitor
- **Request Volume**: Track API usage patterns - **Request Volume**: Track API usage patterns
- **Error Rates**: Monitor authentication and authorization failures - **Error Rates**: Monitor authentication and authorization failures
- **Query Performance**: Track slow queries and optimize - **Query Performance**: Track slow queries and optimize
- **Data Growth**: Monitor audit log size and plan retention - **Data Growth**: Monitor audit log size and plan retention
### Alert Conditions ### Alert Conditions
- **High Error Rates**: >5% of requests failing - **High Error Rates**: >5% of requests failing
- **Unusual Access Patterns**: Off-hours access, high volume - **Unusual Access Patterns**: Off-hours access, high-volume usage
- **Performance Degradation**: Query times >2 seconds - **Performance Degradation**: Query times >2 seconds
- **Security Events**: Multiple failed admin access attempts - **Security Events**: Multiple failed admin access attempts
## Best Practices ## Best Practices
### Security ### Security
- Always validate user permissions before displaying UI - Always validate user permissions before displaying UI
- Log all administrative access to audit logs - Log all administrative access to audit logs
- Use HTTPS in production environments - Use HTTPS in production environments
- Implement proper error handling to avoid information leakage - Implement proper error handling to avoid information leakage
### Performance ### Performance
- Use appropriate page sizes (25-50 records typical) - Use appropriate page sizes (25-50 records typical)
- Implement client-side pagination for better UX - Implement client-side pagination for better UX
- Cache results only in memory, never persist - Cache results only in memory, never persist
- Use date range filters to limit query scope - Use date range filters to limit query scope
### User Experience ### User Experience
- Provide clear filtering options in the UI - Provide clear filtering options in the UI
- Show loading states for long-running queries - Show loading states for long-running queries
- Implement export functionality for reports - Implement export functionality for reports

View File

@@ -28,6 +28,7 @@ X-CSRF-Token: <csrf-token>
``` ```
Get CSRF token: Get CSRF token:
```http ```http
GET /api/csrf-token GET /api/csrf-token
``` ```
@@ -35,10 +36,12 @@ GET /api/csrf-token
## API Endpoints Overview ## API Endpoints Overview
### Public Endpoints ### Public Endpoints
- `POST /api/csp-report` - CSP violation reporting (no auth required) - `POST /api/csp-report` - CSP violation reporting (no auth required)
- `OPTIONS /api/csp-report` - CORS preflight - `OPTIONS /api/csp-report` - CORS preflight
### Authentication Endpoints ### Authentication Endpoints
- `POST /api/auth/[...nextauth]` - NextAuth.js authentication - `POST /api/auth/[...nextauth]` - NextAuth.js authentication
- `GET /api/csrf-token` - Get CSRF token - `GET /api/csrf-token` - Get CSRF token
- `POST /api/register` - User registration - `POST /api/register` - User registration
@@ -46,12 +49,14 @@ GET /api/csrf-token
- `POST /api/reset-password` - Password reset completion - `POST /api/reset-password` - Password reset completion
### Admin Endpoints (ADMIN role required) ### Admin Endpoints (ADMIN role required)
- `GET /api/admin/audit-logs` - Retrieve audit logs - `GET /api/admin/audit-logs` - Retrieve audit logs
- `POST /api/admin/audit-logs/retention` - Manage audit log retention - `POST /api/admin/audit-logs/retention` - Manage audit log retention
- `GET /api/admin/batch-monitoring` - Batch processing monitoring - `GET /api/admin/batch-monitoring` - Batch processing monitoring
- `POST /api/admin/batch-monitoring/{id}/retry` - Retry failed batch job - `POST /api/admin/batch-monitoring/{id}/retry` - Retry failed batch job
### Platform Admin Endpoints (Platform admin only) ### Platform Admin Endpoints (Platform admin only)
- `GET /api/admin/security-monitoring` - Security monitoring metrics - `GET /api/admin/security-monitoring` - Security monitoring metrics
- `POST /api/admin/security-monitoring` - Update security configuration - `POST /api/admin/security-monitoring` - Update security configuration
- `GET /api/admin/security-monitoring/alerts` - Alert management - `GET /api/admin/security-monitoring/alerts` - Alert management
@@ -60,16 +65,19 @@ GET /api/csrf-token
- `POST /api/admin/security-monitoring/threat-analysis` - Threat analysis - `POST /api/admin/security-monitoring/threat-analysis` - Threat analysis
### Security Monitoring Endpoints ### Security Monitoring Endpoints
- `GET /api/csp-metrics` - CSP violation metrics - `GET /api/csp-metrics` - CSP violation metrics
- `POST /api/csp-report` - CSP violation reporting - `POST /api/csp-report` - CSP violation reporting
### Dashboard Endpoints ### Dashboard Endpoints
- `GET /api/dashboard/sessions` - Session data - `GET /api/dashboard/sessions` - Session data
- `GET /api/dashboard/session/{id}` - Individual session details - `GET /api/dashboard/session/{id}` - Individual session details
- `GET /api/dashboard/metrics` - Dashboard metrics - `GET /api/dashboard/metrics` - Dashboard metrics
- `GET /api/dashboard/config` - Dashboard configuration - `GET /api/dashboard/config` - Dashboard configuration
### Platform Management ### Platform Management
- `GET /api/platform/companies` - Company management - `GET /api/platform/companies` - Company management
- `POST /api/platform/companies` - Create company - `POST /api/platform/companies` - Create company
- `GET /api/platform/companies/{id}` - Company details - `GET /api/platform/companies/{id}` - Company details
@@ -77,6 +85,7 @@ GET /api/csrf-token
- `POST /api/platform/companies/{id}/users` - Add company user - `POST /api/platform/companies/{id}/users` - Add company user
### tRPC Endpoints ### tRPC Endpoints
- `POST /api/trpc/[trpc]` - tRPC procedure calls - `POST /api/trpc/[trpc]` - tRPC procedure calls
## Detailed Endpoint Documentation ## Detailed Endpoint Documentation
@@ -84,6 +93,7 @@ GET /api/csrf-token
### Admin Audit Logs ### Admin Audit Logs
#### Get Audit Logs #### Get Audit Logs
```http ```http
GET /api/admin/audit-logs GET /api/admin/audit-logs
``` ```
@@ -91,6 +101,7 @@ GET /api/admin/audit-logs
**Authorization**: ADMIN role required **Authorization**: ADMIN role required
**Query Parameters**: **Query Parameters**:
- `page` (number, optional): Page number (default: 1) - `page` (number, optional): Page number (default: 1)
- `limit` (number, optional): Records per page, max 100 (default: 50) - `limit` (number, optional): Records per page, max 100 (default: 50)
- `eventType` (string, optional): Filter by event type - `eventType` (string, optional): Filter by event type
@@ -101,6 +112,7 @@ GET /api/admin/audit-logs
- `endDate` (string, optional): End date (ISO 8601) - `endDate` (string, optional): End date (ISO 8601)
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -121,6 +133,7 @@ GET /api/admin/audit-logs
**Rate Limit**: Inherits from auth rate limiting **Rate Limit**: Inherits from auth rate limiting
#### Manage Audit Log Retention #### Manage Audit Log Retention
```http ```http
POST /api/admin/audit-logs/retention POST /api/admin/audit-logs/retention
``` ```
@@ -128,6 +141,7 @@ POST /api/admin/audit-logs/retention
**Authorization**: ADMIN role required **Authorization**: ADMIN role required
**Request Body**: **Request Body**:
```json ```json
{ {
"action": "cleanup" | "configure" | "status", "action": "cleanup" | "configure" | "status",
@@ -137,6 +151,7 @@ POST /api/admin/audit-logs/retention
``` ```
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -152,6 +167,7 @@ POST /api/admin/audit-logs/retention
### Security Monitoring ### Security Monitoring
#### Get Security Metrics #### Get Security Metrics
```http ```http
GET /api/admin/security-monitoring GET /api/admin/security-monitoring
``` ```
@@ -159,12 +175,14 @@ GET /api/admin/security-monitoring
**Authorization**: Platform admin required **Authorization**: Platform admin required
**Query Parameters**: **Query Parameters**:
- `startDate` (string, optional): Start date (ISO 8601) - `startDate` (string, optional): Start date (ISO 8601)
- `endDate` (string, optional): End date (ISO 8601) - `endDate` (string, optional): End date (ISO 8601)
- `companyId` (string, optional): Filter by company - `companyId` (string, optional): Filter by company
- `severity` (string, optional): Filter by severity - `severity` (string, optional): Filter by severity
**Response**: **Response**:
```json ```json
{ {
"metrics": { "metrics": {
@@ -180,6 +198,7 @@ GET /api/admin/security-monitoring
``` ```
#### Update Security Configuration #### Update Security Configuration
```http ```http
POST /api/admin/security-monitoring POST /api/admin/security-monitoring
``` ```
@@ -187,6 +206,7 @@ POST /api/admin/security-monitoring
**Authorization**: Platform admin required **Authorization**: Platform admin required
**Request Body**: **Request Body**:
```json ```json
{ {
"thresholds": { "thresholds": {
@@ -203,6 +223,7 @@ POST /api/admin/security-monitoring
### CSP Monitoring ### CSP Monitoring
#### CSP Violation Reporting #### CSP Violation Reporting
```http ```http
POST /api/csp-report POST /api/csp-report
``` ```
@@ -210,9 +231,11 @@ POST /api/csp-report
**Authorization**: None (public endpoint) **Authorization**: None (public endpoint)
**Headers**: **Headers**:
- `Content-Type`: `application/csp-report` or `application/json` - `Content-Type`: `application/csp-report` or `application/json`
**Request Body** (automatic from browser): **Request Body** (automatic from browser):
```json ```json
{ {
"csp-report": { "csp-report": {
@@ -230,6 +253,7 @@ POST /api/csp-report
**Response**: `204 No Content` **Response**: `204 No Content`
#### Get CSP Metrics #### Get CSP Metrics
```http ```http
GET /api/csp-metrics GET /api/csp-metrics
``` ```
@@ -237,12 +261,14 @@ GET /api/csp-metrics
**Authorization**: Admin role required **Authorization**: Admin role required
**Query Parameters**: **Query Parameters**:
- `timeRange` (string, optional): Time range (1h, 6h, 24h, 7d, 30d) - `timeRange` (string, optional): Time range (1h, 6h, 24h, 7d, 30d)
- `format` (string, optional): Response format (json, csv) - `format` (string, optional): Response format (json, csv)
- `groupBy` (string, optional): Group by field (hour, directive, etc.) - `groupBy` (string, optional): Group by field (hour, directive, etc.)
- `includeDetails` (boolean, optional): Include violation details - `includeDetails` (boolean, optional): Include violation details
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -264,6 +290,7 @@ GET /api/csp-metrics
### Batch Monitoring ### Batch Monitoring
#### Get Batch Monitoring Data #### Get Batch Monitoring Data
```http ```http
GET /api/admin/batch-monitoring GET /api/admin/batch-monitoring
``` ```
@@ -271,6 +298,7 @@ GET /api/admin/batch-monitoring
**Authorization**: ADMIN role required **Authorization**: ADMIN role required
**Query Parameters**: **Query Parameters**:
- `timeRange` (string, optional): Time range (1h, 6h, 24h, 7d, 30d) - `timeRange` (string, optional): Time range (1h, 6h, 24h, 7d, 30d)
- `status` (string, optional): Filter by status (pending, completed, failed) - `status` (string, optional): Filter by status (pending, completed, failed)
- `jobType` (string, optional): Filter by job type - `jobType` (string, optional): Filter by job type
@@ -279,6 +307,7 @@ GET /api/admin/batch-monitoring
- `limit` (number, optional): Records per page - `limit` (number, optional): Records per page
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -297,6 +326,7 @@ GET /api/admin/batch-monitoring
``` ```
#### Retry Batch Job #### Retry Batch Job
```http ```http
POST /api/admin/batch-monitoring/{jobId}/retry POST /api/admin/batch-monitoring/{jobId}/retry
``` ```
@@ -304,6 +334,7 @@ POST /api/admin/batch-monitoring/{jobId}/retry
**Authorization**: ADMIN role required **Authorization**: ADMIN role required
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -318,6 +349,7 @@ POST /api/admin/batch-monitoring/{jobId}/retry
### CSRF Token ### CSRF Token
#### Get CSRF Token #### Get CSRF Token
```http ```http
GET /api/csrf-token GET /api/csrf-token
``` ```
@@ -325,6 +357,7 @@ GET /api/csrf-token
**Authorization**: None **Authorization**: None
**Response**: **Response**:
```json ```json
{ {
"csrfToken": "abc123..." "csrfToken": "abc123..."
@@ -332,11 +365,13 @@ GET /api/csrf-token
``` ```
**Headers Set**: **Headers Set**:
- `Set-Cookie`: HTTP-only CSRF token cookie - `Set-Cookie`: HTTP-only CSRF token cookie
### Authentication ### Authentication
#### User Registration #### User Registration
```http ```http
POST /api/register POST /api/register
``` ```
@@ -344,9 +379,11 @@ POST /api/register
**Authorization**: None **Authorization**: None
**Headers Required**: **Headers Required**:
- `X-CSRF-Token`: CSRF token - `X-CSRF-Token`: CSRF token
**Request Body**: **Request Body**:
```json ```json
{ {
"email": "user@example.com", "email": "user@example.com",
@@ -359,6 +396,7 @@ POST /api/register
**Rate Limit**: 3 attempts per hour per IP **Rate Limit**: 3 attempts per hour per IP
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -368,6 +406,7 @@ POST /api/register
``` ```
#### Password Reset Request #### Password Reset Request
```http ```http
POST /api/forgot-password POST /api/forgot-password
``` ```
@@ -375,9 +414,11 @@ POST /api/forgot-password
**Authorization**: None **Authorization**: None
**Headers Required**: **Headers Required**:
- `X-CSRF-Token`: CSRF token - `X-CSRF-Token`: CSRF token
**Request Body**: **Request Body**:
```json ```json
{ {
"email": "user@example.com" "email": "user@example.com"
@@ -387,6 +428,7 @@ POST /api/forgot-password
**Rate Limit**: 5 attempts per 15 minutes per IP **Rate Limit**: 5 attempts per 15 minutes per IP
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -395,6 +437,7 @@ POST /api/forgot-password
``` ```
#### Password Reset Completion #### Password Reset Completion
```http ```http
POST /api/reset-password POST /api/reset-password
``` ```
@@ -402,9 +445,11 @@ POST /api/reset-password
**Authorization**: None **Authorization**: None
**Headers Required**: **Headers Required**:
- `X-CSRF-Token`: CSRF token - `X-CSRF-Token`: CSRF token
**Request Body**: **Request Body**:
```json ```json
{ {
"token": "reset-token-123", "token": "reset-token-123",
@@ -413,6 +458,7 @@ POST /api/reset-password
``` ```
**Response**: **Response**:
```json ```json
{ {
"success": true, "success": true,
@@ -464,16 +510,19 @@ POST /api/reset-password
## Rate Limiting ## Rate Limiting
### Authentication Endpoints ### Authentication Endpoints
- **Login**: 5 attempts per 15 minutes per IP - **Login**: 5 attempts per 15 minutes per IP
- **Registration**: 3 attempts per hour per IP - **Registration**: 3 attempts per hour per IP
- **Password Reset**: 5 attempts per 15 minutes per IP - **Password Reset**: 5 attempts per 15 minutes per IP
### Security Endpoints ### Security Endpoints
- **CSP Reports**: 10 reports per minute per IP - **CSP Reports**: 10 reports per minute per IP
- **Admin Endpoints**: 60 requests per minute per user - **Admin Endpoints**: 60 requests per minute per user
- **Security Monitoring**: 30 requests per minute per user - **Security Monitoring**: 30 requests per minute per user
### General API ### General API
- **Dashboard Endpoints**: 120 requests per minute per user - **Dashboard Endpoints**: 120 requests per minute per user
- **Platform Management**: 60 requests per minute per user - **Platform Management**: 60 requests per minute per user
@@ -492,13 +541,16 @@ Content-Security-Policy: [CSP directives]
## CORS Configuration ## CORS Configuration
### Allowed Origins ### Allowed Origins
- Development: `http://localhost:3000` - Development: `http://localhost:3000`
- Production: `https://your-domain.com` - Production: `https://your-domain.com`
### Allowed Methods ### Allowed Methods
- `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS` - `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`
### Allowed Headers ### Allowed Headers
- `Content-Type`, `Authorization`, `X-CSRF-Token`, `X-Requested-With` - `Content-Type`, `Authorization`, `X-CSRF-Token`, `X-Requested-With`
## Pagination ## Pagination
@@ -520,12 +572,14 @@ Content-Security-Policy: [CSP directives]
``` ```
### Pagination Parameters ### Pagination Parameters
- `page`: Page number (1-based, default: 1) - `page`: Page number (1-based, default: 1)
- `limit`: Records per page (default: 50, max: 100) - `limit`: Records per page (default: 50, max: 100)
## Filtering and Sorting ## Filtering and Sorting
### Common Filter Parameters ### Common Filter Parameters
- `startDate` / `endDate`: Date range filtering (ISO 8601) - `startDate` / `endDate`: Date range filtering (ISO 8601)
- `status`: Status filtering - `status`: Status filtering
- `userId` / `companyId`: Entity filtering - `userId` / `companyId`: Entity filtering
@@ -533,12 +587,14 @@ Content-Security-Policy: [CSP directives]
- `severity`: Severity level filtering - `severity`: Severity level filtering
### Sorting Parameters ### Sorting Parameters
- `sortBy`: Field to sort by - `sortBy`: Field to sort by
- `sortOrder`: `asc` or `desc` (default: `desc`) - `sortOrder`: `asc` or `desc` (default: `desc`)
## Response Caching ## Response Caching
### Cache Headers ### Cache Headers
```http ```http
Cache-Control: no-cache, no-store, must-revalidate Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache Pragma: no-cache
@@ -546,6 +602,7 @@ Expires: 0
``` ```
### Cache Strategy ### Cache Strategy
- **Security data**: Never cached - **Security data**: Never cached
- **Static data**: Browser cache for 5 minutes - **Static data**: Browser cache for 5 minutes
- **User data**: No cache for security - **User data**: No cache for security
@@ -553,10 +610,12 @@ Expires: 0
## API Versioning ## API Versioning
### Current Version ### Current Version
- Version: `v1` (implied, no version prefix required) - Version: `v1` (implied, no version prefix required)
- Introduced: January 2025 - Introduced: January 2025
### Future Versioning ### Future Versioning
- Breaking changes will introduce new versions - Breaking changes will introduce new versions
- Format: `/api/v2/endpoint` - Format: `/api/v2/endpoint`
- Backward compatibility maintained for 12 months - Backward compatibility maintained for 12 months

View File

@@ -9,18 +9,21 @@ The Batch Monitoring Dashboard provides real-time visibility into the OpenAI Bat
## Features ## Features
### Real-time Monitoring ### Real-time Monitoring
- **Job Status Tracking**: Monitor batch jobs from creation to completion - **Job Status Tracking**: Monitor batch jobs from creation to completion
- **Queue Management**: View pending, running, and completed batch queues - **Queue Management**: View pending, running, and completed batch queues
- **Processing Metrics**: Track throughput, success rates, and error patterns - **Processing Metrics**: Track throughput, success rates, and error patterns
- **Cost Analysis**: Monitor API costs and savings compared to individual requests - **Cost Analysis**: Monitor API costs and savings compared to individual requests
### Performance Analytics ### Performance Analytics
- **Batch Efficiency**: Analyze batch size optimization and processing times - **Batch Efficiency**: Analyze batch size optimization and processing times
- **Success Rates**: Track completion and failure rates across different job types - **Success Rates**: Track completion and failure rates across different job types
- **Resource Utilization**: Monitor API quota usage and rate limiting - **Resource Utilization**: Monitor API quota usage and rate limiting
- **Historical Trends**: View processing patterns over time - **Historical Trends**: View processing patterns over time
### Administrative Controls ### Administrative Controls
- **Manual Intervention**: Pause, resume, or cancel batch operations - **Manual Intervention**: Pause, resume, or cancel batch operations
- **Priority Management**: Adjust processing priorities for urgent requests - **Priority Management**: Adjust processing priorities for urgent requests
- **Error Handling**: Review and retry failed batch operations - **Error Handling**: Review and retry failed batch operations
@@ -132,6 +135,7 @@ const data = await response.json();
The main dashboard component (`components/admin/BatchMonitoringDashboard.tsx`) provides: The main dashboard component (`components/admin/BatchMonitoringDashboard.tsx`) provides:
#### Key Metrics Cards #### Key Metrics Cards
```tsx ```tsx
// Real-time overview cards // Real-time overview cards
<MetricCard <MetricCard
@@ -157,6 +161,7 @@ The main dashboard component (`components/admin/BatchMonitoringDashboard.tsx`) p
``` ```
#### Queue Status Visualization #### Queue Status Visualization
```tsx ```tsx
// Visual representation of batch job queues // Visual representation of batch job queues
<QueueStatusChart <QueueStatusChart
@@ -168,6 +173,7 @@ The main dashboard component (`components/admin/BatchMonitoringDashboard.tsx`) p
``` ```
#### Performance Charts #### Performance Charts
```tsx ```tsx
// Processing throughput over time // Processing throughput over time
<ThroughputChart <ThroughputChart
@@ -183,6 +189,7 @@ The main dashboard component (`components/admin/BatchMonitoringDashboard.tsx`) p
``` ```
#### Job Management Table #### Job Management Table
```tsx ```tsx
// Detailed job listing with actions // Detailed job listing with actions
<BatchJobTable <BatchJobTable
@@ -400,6 +407,7 @@ async function configureAlerts(alertConfig) {
### Common Issues ### Common Issues
#### High Error Rates #### High Error Rates
```javascript ```javascript
// Investigate high error rates // Investigate high error rates
async function investigateErrors() { async function investigateErrors() {
@@ -424,6 +432,7 @@ async function investigateErrors() {
``` ```
#### Slow Processing #### Slow Processing
```javascript ```javascript
// Analyze processing bottlenecks // Analyze processing bottlenecks
async function analyzePerformance() { async function analyzePerformance() {
@@ -457,6 +466,7 @@ async function analyzePerformance() {
### Performance Optimization ### Performance Optimization
#### Batch Size Optimization #### Batch Size Optimization
```javascript ```javascript
// Analyze optimal batch sizes // Analyze optimal batch sizes
async function optimizeBatchSizes() { async function optimizeBatchSizes() {
@@ -497,6 +507,7 @@ async function optimizeBatchSizes() {
## Integration with Existing Systems ## Integration with Existing Systems
### Security Audit Integration ### Security Audit Integration
All batch monitoring activities are logged through the security audit system: All batch monitoring activities are logged through the security audit system:
```javascript ```javascript
@@ -510,6 +521,7 @@ await securityAuditLogger.logPlatformAdmin(
``` ```
### Rate Limiting Integration ### Rate Limiting Integration
Monitoring API endpoints use the existing rate limiting system: Monitoring API endpoints use the existing rate limiting system:
```javascript ```javascript

View File

@@ -32,6 +32,7 @@ The following composite indexes were added to the `AIProcessingRequest` table in
### Query Performance Impact ### Query Performance Impact
These indexes specifically optimize: These indexes specifically optimize:
- Finding pending requests by status and creation time - Finding pending requests by status and creation time
- Batch-related lookups by batch ID - Batch-related lookups by batch ID
- Combined status and batch filtering operations - Combined status and batch filtering operations
@@ -41,6 +42,7 @@ These indexes specifically optimize:
### 1. Selective Data Fetching ### 1. Selective Data Fetching
**Before:** **Before:**
```typescript ```typescript
// Loaded full session with all messages // Loaded full session with all messages
include: { include: {
@@ -55,6 +57,7 @@ include: {
``` ```
**After:** **After:**
```typescript ```typescript
// Only essential data with message count // Only essential data with message count
include: { include: {
@@ -86,6 +89,7 @@ class CompanyCache {
### 3. Batch Operations ### 3. Batch Operations
**Before:** N+1 queries for each company **Before:** N+1 queries for each company
```typescript ```typescript
// Sequential processing per company // Sequential processing per company
for (const company of companies) { for (const company of companies) {
@@ -95,6 +99,7 @@ for (const company of companies) {
``` ```
**After:** Single query for all companies **After:** Single query for all companies
```typescript ```typescript
// Batch query for all companies at once // Batch query for all companies at once
const allRequests = await prisma.aIProcessingRequest.findMany({ const allRequests = await prisma.aIProcessingRequest.findMany({
@@ -169,11 +174,13 @@ class PerformanceTracker {
## Files Modified ## Files Modified
### New Files ### New Files
- `lib/batchProcessorOptimized.ts` - Optimized query implementations - `lib/batchProcessorOptimized.ts` - Optimized query implementations
- `lib/batchSchedulerOptimized.ts` - Optimized scheduler - `lib/batchSchedulerOptimized.ts` - Optimized scheduler
- `lib/batchProcessorIntegration.ts` - Integration layer with fallback - `lib/batchProcessorIntegration.ts` - Integration layer with fallback
### Modified Files ### Modified Files
- `prisma/schema.prisma` - Added composite indexes - `prisma/schema.prisma` - Added composite indexes
- `server.ts` - Updated to use integration layer - `server.ts` - Updated to use integration layer
- `app/api/admin/batch-monitoring/route.ts` - Updated import - `app/api/admin/batch-monitoring/route.ts` - Updated import

View File

@@ -426,6 +426,7 @@ CSP_ALERT_THRESHOLD=5 # violations per 10 minutes
### Privacy Protection ### Privacy Protection
**⚠️ Data Collection Notice:** **⚠️ Data Collection Notice:**
- **IP addresses** are collected and stored in memory for security monitoring - **IP addresses** are collected and stored in memory for security monitoring
- **User agent strings** are stored for browser compatibility analysis - **User agent strings** are stored for browser compatibility analysis
- **Legal basis**: Legitimate interest for security incident detection and prevention - **Legal basis**: Legitimate interest for security incident detection and prevention
@@ -433,10 +434,11 @@ CSP_ALERT_THRESHOLD=5 # violations per 10 minutes
- **Data minimization**: Only violation-related metadata is retained, not page content - **Data minimization**: Only violation-related metadata is retained, not page content
**Planned Privacy Enhancements:** **Planned Privacy Enhancements:**
- IP anonymization options for GDPR compliance (roadmap) - IP anonymization options for GDPR compliance (roadmap)
- User agent sanitization to remove sensitive information (roadmap) - User agent sanitization to remove sensitive information (roadmap)
### Rate Limiting Protection ### Rate-Limiting Protection
- **Per-IP limits** prevent DoS attacks on reporting endpoint - **Per-IP limits** prevent DoS attacks on reporting endpoint
- **Content-type validation** ensures proper report format - **Content-type validation** ensures proper report format

View File

@@ -7,6 +7,7 @@ Successfully refactored the session processing pipeline from a simple status-bas
## Problems Solved ## Problems Solved
### Original Issues ### Original Issues
1. **Inconsistent Status Tracking**: The old system used a simple enum on SessionImport that didn't properly track the multi-stage processing pipeline 1. **Inconsistent Status Tracking**: The old system used a simple enum on SessionImport that didn't properly track the multi-stage processing pipeline
2. **Poor Error Visibility**: Error messages were buried in the SessionImport table and not easily accessible 2. **Poor Error Visibility**: Error messages were buried in the SessionImport table and not easily accessible
3. **No Stage-Specific Tracking**: The system couldn't track which specific stage of processing failed 3. **No Stage-Specific Tracking**: The system couldn't track which specific stage of processing failed
@@ -14,6 +15,7 @@ Successfully refactored the session processing pipeline from a simple status-bas
5. **Linting Errors**: Multiple TypeScript files referencing removed database fields 5. **Linting Errors**: Multiple TypeScript files referencing removed database fields
### Schema Changes Made ### Schema Changes Made
- **Removed** old `status`, `errorMsg`, and `processedAt` columns from SessionImport - **Removed** old `status`, `errorMsg`, and `processedAt` columns from SessionImport
- **Removed** `processed` field from Session - **Removed** `processed` field from Session
- **Added** new `SessionProcessingStatus` table with granular stage tracking - **Added** new `SessionProcessingStatus` table with granular stage tracking
@@ -22,6 +24,7 @@ Successfully refactored the session processing pipeline from a simple status-bas
## New Processing Pipeline ## New Processing Pipeline
### Processing Stages ### Processing Stages
```typescript ```typescript
enum ProcessingStage { enum ProcessingStage {
CSV_IMPORT // SessionImport created CSV_IMPORT // SessionImport created
@@ -39,7 +42,9 @@ enum ProcessingStatus {
### Key Components ### Key Components
#### 1. ProcessingStatusManager #### 1. ProcessingStatusManager
Centralized class for managing processing status with methods: Centralized class for managing processing status with methods:
- `initializeSession()` - Set up processing status for new sessions - `initializeSession()` - Set up processing status for new sessions
- `startStage()`, `completeStage()`, `failStage()`, `skipStage()` - Stage management - `startStage()`, `completeStage()`, `failStage()`, `skipStage()` - Stage management
- `getSessionsNeedingProcessing()` - Query sessions by stage and status - `getSessionsNeedingProcessing()` - Query sessions by stage and status
@@ -48,12 +53,14 @@ Centralized class for managing processing status with methods:
- `resetStageForRetry()` - Reset failed stages - `resetStageForRetry()` - Reset failed stages
#### 2. Updated Processing Scheduler #### 2. Updated Processing Scheduler
- Integrated with new `ProcessingStatusManager` - Integrated with new `ProcessingStatusManager`
- Tracks AI analysis and question extraction stages - Tracks AI analysis and question extraction stages
- Records detailed processing metadata - Records detailed processing metadata
- Proper error handling and retry capabilities - Proper error handling and retry capabilities
#### 3. Migration System #### 3. Migration System
- Successfully migrated all 109 existing sessions - Successfully migrated all 109 existing sessions
- Determined current state based on existing data - Determined current state based on existing data
- Preserved all existing functionality - Preserved all existing functionality
@@ -61,6 +68,7 @@ Centralized class for managing processing status with methods:
## Current Pipeline Status ## Current Pipeline Status
After migration and refactoring: After migration and refactoring:
- **CSV_IMPORT**: 109 completed - **CSV_IMPORT**: 109 completed
- **TRANSCRIPT_FETCH**: 109 completed - **TRANSCRIPT_FETCH**: 109 completed
- **SESSION_CREATION**: 109 completed - **SESSION_CREATION**: 109 completed
@@ -70,18 +78,21 @@ After migration and refactoring:
## Files Updated/Created ## Files Updated/Created
### New Files ### New Files
- `lib/processingStatusManager.ts` - Core processing status management - `lib/processingStatusManager.ts` - Core processing status management
- `check-refactored-pipeline-status.ts` - New pipeline status checker - `check-refactored-pipeline-status.ts` - New pipeline status checker
- `migrate-to-refactored-system.ts` - Migration script - `migrate-to-refactored-system.ts` - Migration script
- `docs/processing-system-refactor.md` - This documentation - `docs/processing-system-refactor.md` - This documentation
### Updated Files ### Updated Files
- `prisma/schema.prisma` - Added new processing status tables - `prisma/schema.prisma` - Added new processing status tables
- `lib/processingScheduler.ts` - Integrated with new status system - `lib/processingScheduler.ts` - Integrated with new status system
- `debug-import-status.ts` - Updated to use new system - `debug-import-status.ts` - Updated to use new system
- `fix-import-status.ts` - Updated to use new system - `fix-import-status.ts` - Updated to use new system
### Removed Files ### Removed Files
- `check-pipeline-status.ts` - Replaced by refactored version - `check-pipeline-status.ts` - Replaced by refactored version
## Benefits Achieved ## Benefits Achieved
@@ -97,21 +108,25 @@ After migration and refactoring:
## Usage Examples ## Usage Examples
### Check Pipeline Status ### Check Pipeline Status
```bash ```bash
npx tsx check-refactored-pipeline-status.ts npx tsx check-refactored-pipeline-status.ts
``` ```
### Debug Processing Issues ### Debug Processing Issues
```bash ```bash
npx tsx debug-import-status.ts npx tsx debug-import-status.ts
``` ```
### Fix/Retry Failed Sessions ### Fix/Retry Failed Sessions
```bash ```bash
npx tsx fix-import-status.ts npx tsx fix-import-status.ts
``` ```
### Process Sessions ### Process Sessions
```bash ```bash
npx tsx test-ai-processing.ts npx tsx test-ai-processing.ts
``` ```

View File

@@ -99,12 +99,14 @@ node scripts/manual-triggers.js both
``` ```
2. **If "Sessions with transcript" is 0:** 2. **If "Sessions with transcript" is 0:**
- Sessions exist but transcripts haven't been fetched yet
- Run session refresh: `node scripts/manual-triggers.js refresh` - Sessions exist but transcripts haven't been fetched yet
- Run session refresh: `node scripts/manual-triggers.js refresh`
3. **If "Ready for processing" is 0 but "Sessions with transcript" > 0:** 3. **If "Ready for processing" is 0 but "Sessions with transcript" > 0:**
- All sessions with transcripts have already been processed
- Check if `OPENAI_API_KEY` is set in environment - All sessions with transcripts have already been processed
- Check if `OPENAI_API_KEY` is set in environment
### Common Issues ### Common Issues

View File

@@ -48,7 +48,7 @@ Comprehensive CSP implementation with the following directives:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests
``` ```
#### Key CSP Directives: #### Key CSP Directives
- **default-src 'self'**: Restrictive default for all resource types - **default-src 'self'**: Restrictive default for all resource types
- **script-src 'self' 'unsafe-eval' 'unsafe-inline'**: Allows Next.js dev tools and React functionality - **script-src 'self' 'unsafe-eval' 'unsafe-inline'**: Allows Next.js dev tools and React functionality
@@ -220,8 +220,8 @@ Security headers are supported by:
### Debug Tools ### Debug Tools
- Browser DevTools Security tab - Browser DevTools Security tab
- CSP Evaluator: https://csp-evaluator.withgoogle.com/ - CSP Evaluator: <https://csp-evaluator.withgoogle.com/>
- Security Headers Scanner: https://securityheaders.com/ - Security Headers Scanner: <https://securityheaders.com/>
## References ## References

View File

@@ -149,20 +149,24 @@ export default async function RootLayout({ children }: { children: ReactNode })
### 2. Content Source Restrictions ### 2. Content Source Restrictions
#### Script Sources #### Script Sources
- **Production**: Only `'self'` and nonce-approved scripts - **Production**: Only `'self'` and nonce-approved scripts
- **Development**: Additional `'unsafe-eval'` for dev tools - **Development**: Additional `'unsafe-eval'` for dev tools
- **Blocked**: All external CDNs, inline scripts without nonce - **Blocked**: All external CDNs, inline scripts without nonce
#### Style Sources #### Style Sources
- **Production**: Nonce-based inline styles preferred - **Production**: Nonce-based inline styles preferred
- **Fallback**: `'unsafe-inline'` for TailwindCSS compatibility - **Fallback**: `'unsafe-inline'` for TailwindCSS compatibility
- **External**: Only self-hosted stylesheets - **External**: Only self-hosted stylesheets
#### Image Sources #### Image Sources
- **Allowed**: Self, data URIs, schema.org, application domain - **Allowed**: Self, data URIs, schema.org, application domain
- **Blocked**: All other external domains - **Blocked**: All other external domains
#### Connection Sources #### Connection Sources
- **Production**: Self, OpenAI API, application domain - **Production**: Self, OpenAI API, application domain
- **Development**: Additional WebSocket for HMR - **Development**: Additional WebSocket for HMR
- **Blocked**: All other external connections - **Blocked**: All other external connections
@@ -170,6 +174,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
### 3. XSS Protection Mechanisms ### 3. XSS Protection Mechanisms
#### Inline Script Prevention #### Inline Script Prevention
```javascript ```javascript
// Blocked by CSP // Blocked by CSP
<script>alert('xss')</script> <script>alert('xss')</script>
@@ -179,18 +184,21 @@ export default async function RootLayout({ children }: { children: ReactNode })
``` ```
#### Object Injection Prevention #### Object Injection Prevention
```javascript ```javascript
// Completely blocked // Completely blocked
object-src 'none' object-src 'none'
``` ```
#### Base Tag Injection Prevention #### Base Tag Injection Prevention
```javascript ```javascript
// Restricted to same origin // Restricted to same origin
base-uri 'self' base-uri 'self'
``` ```
#### Clickjacking Protection #### Clickjacking Protection
```javascript ```javascript
// No framing allowed // No framing allowed
frame-ancestors 'none' frame-ancestors 'none'
@@ -239,6 +247,7 @@ CSP violations are automatically reported to `/api/csp-report`:
### Monitoring Dashboard ### Monitoring Dashboard
Violations are logged with: Violations are logged with:
- Timestamp and source IP - Timestamp and source IP
- User agent and referer - User agent and referer
- Violation type and blocked content - Violation type and blocked content
@@ -271,6 +280,7 @@ pnpm test:csp:full
### Security Scoring ### Security Scoring
The validation framework provides a security score: The validation framework provides a security score:
- **90-100%**: Excellent implementation - **90-100%**: Excellent implementation
- **80-89%**: Good with minor improvements needed - **80-89%**: Good with minor improvements needed
- **70-79%**: Needs attention - **70-79%**: Needs attention

View File

@@ -5,9 +5,11 @@ This document outlines the fixes applied to resolve TypeScript compilation error
## Issues Resolved ## Issues Resolved
### 1. Missing Type Imports ### 1. Missing Type Imports
**Problem:** `lib/api/index.ts` was missing required type imports **Problem:** `lib/api/index.ts` was missing required type imports
**Error:** `Cannot find name 'APIHandler'`, `Cannot find name 'Permission'` **Error:** `Cannot find name 'APIHandler'`, `Cannot find name 'Permission'`
**Fix:** Added proper imports at the top of the file **Fix:** Added proper imports at the top of the file
```typescript ```typescript
import type { APIContext, APIHandler, APIHandlerOptions } from "./handler"; import type { APIContext, APIHandler, APIHandlerOptions } from "./handler";
import { createAPIHandler } from "./handler"; import { createAPIHandler } from "./handler";
@@ -15,9 +17,11 @@ import { Permission, createPermissionChecker } from "./authorization";
``` ```
### 2. Zod API Breaking Change ### 2. Zod API Breaking Change
**Problem:** Zod error property name changed from `errors` to `issues` **Problem:** Zod error property name changed from `errors` to `issues`
**Error:** `Property 'errors' does not exist on type 'ZodError'` **Error:** `Property 'errors' does not exist on type 'ZodError'`
**Fix:** Updated all references to use `error.issues` instead of `error.errors` **Fix:** Updated all references to use `error.issues` instead of `error.errors`
```typescript ```typescript
// Before // Before
error.errors.map((e) => `${e.path.join(".")}: ${e.message}`) error.errors.map((e) => `${e.path.join(".")}: ${e.message}`)
@@ -26,17 +30,21 @@ error.issues.map((e) => `${e.path.join(".")}: ${e.message}`)
``` ```
### 3. Missing LRU Cache Dependency ### 3. Missing LRU Cache Dependency
**Problem:** `lru-cache` package was missing from dependencies **Problem:** `lru-cache` package was missing from dependencies
**Error:** `Cannot find module 'lru-cache'` **Error:** `Cannot find module 'lru-cache'`
**Fix:** Installed the missing dependency **Fix:** Installed the missing dependency
```bash ```bash
pnpm add lru-cache pnpm add lru-cache
``` ```
### 4. LRU Cache Generic Type Constraints ### 4. LRU Cache Generic Type Constraints
**Problem:** TypeScript generic constraints not satisfied **Problem:** TypeScript generic constraints not satisfied
**Error:** `Type 'K' does not satisfy the constraint '{}'` **Error:** `Type 'K' does not satisfy the constraint '{}'`
**Fix:** Added proper generic type constraints **Fix:** Added proper generic type constraints
```typescript ```typescript
// Before // Before
<K = string, V = any> <K = string, V = any>
@@ -45,9 +53,11 @@ pnpm add lru-cache
``` ```
### 5. Map Iteration ES5 Compatibility ### 5. Map Iteration ES5 Compatibility
**Problem:** Map iteration requires downlevel iteration flag **Problem:** Map iteration requires downlevel iteration flag
**Error:** `can only be iterated through when using the '--downlevelIteration' flag` **Error:** `can only be iterated through when using the '--downlevelIteration' flag`
**Fix:** Used `Array.from()` pattern for compatibility **Fix:** Used `Array.from()` pattern for compatibility
```typescript ```typescript
// Before // Before
for (const [key, value] of map) { ... } for (const [key, value] of map) { ... }
@@ -56,9 +66,11 @@ for (const [key, value] of Array.from(map.entries())) { ... }
``` ```
### 6. Redis Configuration Issues ### 6. Redis Configuration Issues
**Problem:** Invalid Redis socket options **Problem:** Invalid Redis socket options
**Error:** Redis connection failed with unsupported options **Error:** Redis connection failed with unsupported options
**Fix:** Simplified Redis configuration to only include supported options **Fix:** Simplified Redis configuration to only include supported options
```typescript ```typescript
this.client = createClient({ this.client = createClient({
url: env.REDIS_URL, url: env.REDIS_URL,
@@ -69,9 +81,11 @@ this.client = createClient({
``` ```
### 7. Prisma Relationship Naming Mismatches ### 7. Prisma Relationship Naming Mismatches
**Problem:** Code referenced non-existent Prisma relationships **Problem:** Code referenced non-existent Prisma relationships
**Error:** `securityAuditLogs` and `sessionImport` don't exist **Error:** `securityAuditLogs` and `sessionImport` don't exist
**Fix:** Used correct relationship names **Fix:** Used correct relationship names
```typescript ```typescript
// Before // Before
user.securityAuditLogs user.securityAuditLogs
@@ -82,17 +96,21 @@ session.import
``` ```
### 8. Missing Schema Fields ### 8. Missing Schema Fields
**Problem:** Code referenced fields that don't exist in the database schema **Problem:** Code referenced fields that don't exist in the database schema
**Error:** `Property 'userId' does not exist on type` **Error:** `Property 'userId' does not exist on type`
**Fix:** Applied type casting where schema fields were missing **Fix:** Applied type casting where schema fields were missing
```typescript ```typescript
userId: (session as any).userId || null userId: (session as any).userId || null
``` ```
### 9. Deprecated Package Dependencies ### 9. Deprecated Package Dependencies
**Problem:** `critters` package is deprecated and caused build failures **Problem:** `critters` package is deprecated and caused build failures
**Error:** `Cannot find module 'critters'` **Error:** `Cannot find module 'critters'`
**Fix:** Disabled CSS optimization feature that required critters **Fix:** Disabled CSS optimization feature that required critters
```javascript ```javascript
experimental: { experimental: {
optimizeCss: false, // Disabled due to critters dependency optimizeCss: false, // Disabled due to critters dependency
@@ -100,9 +118,11 @@ experimental: {
``` ```
### 10. ESLint vs Biome Conflict ### 10. ESLint vs Biome Conflict
**Problem:** ESLint warnings treated as build errors **Problem:** ESLint warnings treated as build errors
**Error:** Build failed due to linting warnings **Error:** Build failed due to linting warnings
**Fix:** Disabled ESLint during build since Biome is used for linting **Fix:** Disabled ESLint during build since Biome is used for linting
```javascript ```javascript
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
@@ -112,6 +132,7 @@ eslint: {
## Schema Enhancements ## Schema Enhancements
### Enhanced User Management ### Enhanced User Management
Added comprehensive user management fields to the User model: Added comprehensive user management fields to the User model:
```prisma ```prisma
@@ -137,7 +158,9 @@ model User {
``` ```
### Updated Repository Methods ### Updated Repository Methods
Enhanced UserRepository with new methods: Enhanced UserRepository with new methods:
- `updateLastLogin()` - Tracks user login times - `updateLastLogin()` - Tracks user login times
- `incrementFailedLoginAttempts()` - Security feature for account locking - `incrementFailedLoginAttempts()` - Security feature for account locking
- `verifyEmail()` - Email verification management - `verifyEmail()` - Email verification management
@@ -149,26 +172,31 @@ Enhanced UserRepository with new methods:
## Prevention Measures ## Prevention Measures
### 1. Regular Dependency Updates ### 1. Regular Dependency Updates
- Monitor for breaking changes in dependencies like Zod - Monitor for breaking changes in dependencies like Zod
- Use `pnpm outdated` to check for deprecated packages - Use `pnpm outdated` to check for deprecated packages
- Test builds after dependency updates - Test builds after dependency updates
### 2. TypeScript Strict Checking ### 2. TypeScript Strict Checking
- Enable strict TypeScript checking to catch type errors early - Enable strict TypeScript checking to catch type errors early
- Use proper type imports and exports - Use proper type imports and exports
- Avoid `any` types where possible - Avoid `any` types where possible
### 3. Build Pipeline Validation ### 3. Build Pipeline Validation
- Run `pnpm build` before committing - Run `pnpm build` before committing
- Include type checking in CI/CD pipeline - Include type checking in CI/CD pipeline
- Separate linting from build process - Separate linting from build process
### 4. Schema Management ### 4. Schema Management
- Regenerate Prisma client after schema changes: `pnpm prisma:generate` - Regenerate Prisma client after schema changes: `pnpm prisma:generate`
- Validate schema changes with database migrations - Validate schema changes with database migrations
- Use proper TypeScript types for database operations - Use proper TypeScript types for database operations
### 5. Development Workflow ### 5. Development Workflow
```bash ```bash
# Recommended development workflow # Recommended development workflow
pnpm prisma:generate # After schema changes pnpm prisma:generate # After schema changes

View File

@@ -17,7 +17,11 @@ export class APIError extends Error {
message: string, message: string,
public readonly statusCode: number = 500, public readonly statusCode: number = 500,
public readonly code: string = "INTERNAL_ERROR", public readonly code: string = "INTERNAL_ERROR",
public readonly details?: any, public readonly details?:
| Record<string, unknown>
| string[]
| string
| number,
public readonly logLevel: "info" | "warn" | "error" = "error" public readonly logLevel: "info" | "warn" | "error" = "error"
) { ) {
super(message); super(message);
@@ -100,7 +104,10 @@ export class ConflictError extends APIError {
* Database Error - for database operation failures * Database Error - for database operation failures
*/ */
export class DatabaseError extends APIError { export class DatabaseError extends APIError {
constructor(message = "Database operation failed", details?: any) { constructor(
message = "Database operation failed",
details?: Record<string, unknown> | string
) {
super(message, 500, "DATABASE_ERROR", details, "error"); super(message, 500, "DATABASE_ERROR", details, "error");
} }
} }
@@ -112,7 +119,7 @@ export class ExternalServiceError extends APIError {
constructor( constructor(
service: string, service: string,
message = "External service error", message = "External service error",
details?: any details?: Record<string, unknown>
) { ) {
super( super(
`${service} service error: ${message}`, `${service} service error: ${message}`,
@@ -138,7 +145,11 @@ function shouldExposeError(error: unknown): boolean {
/** /**
* Log error with appropriate level * Log error with appropriate level
*/ */
function logError(error: unknown, requestId: string, context?: any): void { function logError(
error: unknown,
requestId: string,
context?: Record<string, unknown>
): void {
const logData = { const logData = {
requestId, requestId,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
@@ -170,7 +181,7 @@ function logError(error: unknown, requestId: string, context?: any): void {
export function handleAPIError( export function handleAPIError(
error: unknown, error: unknown,
requestId?: string, requestId?: string,
context?: any context?: Record<string, unknown>
): NextResponse { ): NextResponse {
const id = requestId || crypto.randomUUID(); const id = requestId || crypto.randomUUID();
@@ -220,7 +231,7 @@ export function handleAPIError(
/** /**
* Async error handler for promise chains * Async error handler for promise chains
*/ */
export function asyncErrorHandler<T extends any[], R>( export function asyncErrorHandler<T extends readonly unknown[], R>(
fn: (...args: T) => Promise<R> fn: (...args: T) => Promise<R>
) { ) {
return async (...args: T): Promise<R> => { return async (...args: T): Promise<R> => {
@@ -237,7 +248,7 @@ export function asyncErrorHandler<T extends any[], R>(
/** /**
* Error boundary for API route handlers * Error boundary for API route handlers
*/ */
export function withErrorHandling<T extends any[], R>( export function withErrorHandling<T extends readonly unknown[], R>(
handler: (...args: T) => Promise<NextResponse> | NextResponse handler: (...args: T) => Promise<NextResponse> | NextResponse
) { ) {
return async (...args: T): Promise<NextResponse> => { return async (...args: T): Promise<NextResponse> => {

View File

@@ -91,10 +91,10 @@ export interface APIHandlerOptions {
/** /**
* API handler function type * API handler function type
*/ */
export type APIHandler<T = any> = ( export type APIHandler<T = unknown> = (
context: APIContext, context: APIContext,
validatedData?: any, validatedData?: unknown,
validatedQuery?: any validatedQuery?: unknown
) => Promise<T>; ) => Promise<T>;
/** /**
@@ -226,7 +226,7 @@ async function validateInput<T>(
if (error instanceof SyntaxError) { if (error instanceof SyntaxError) {
throw new ValidationError(["Invalid JSON in request body"]); throw new ValidationError(["Invalid JSON in request body"]);
} }
throw new ValidationError(error as any); throw new ValidationError(error as z.ZodError);
} }
} }
@@ -239,7 +239,7 @@ function validateQuery<T>(request: NextRequest, schema: z.ZodSchema<T>): T {
const query = Object.fromEntries(searchParams.entries()); const query = Object.fromEntries(searchParams.entries());
return schema.parse(query); return schema.parse(query);
} catch (error) { } catch (error) {
throw new ValidationError(error as any); throw new ValidationError(error as z.ZodError);
} }
} }
@@ -285,7 +285,7 @@ function addCORSHeaders(
/** /**
* Main API handler factory * Main API handler factory
*/ */
export function createAPIHandler<T = any>( export function createAPIHandler<T = unknown>(
handler: APIHandler<T>, handler: APIHandler<T>,
options: APIHandlerOptions = {} options: APIHandlerOptions = {}
) { ) {
@@ -368,7 +368,7 @@ export function createAPIHandler<T = any>(
/** /**
* Utility function for GET endpoints * Utility function for GET endpoints
*/ */
export function createGETHandler<T = any>( export function createGETHandler<T = unknown>(
handler: APIHandler<T>, handler: APIHandler<T>,
options: Omit<APIHandlerOptions, "validateInput"> = {} options: Omit<APIHandlerOptions, "validateInput"> = {}
) { ) {
@@ -381,7 +381,7 @@ export function createGETHandler<T = any>(
/** /**
* Utility function for POST endpoints * Utility function for POST endpoints
*/ */
export function createPOSTHandler<T = any>( export function createPOSTHandler<T = unknown>(
handler: APIHandler<T>, handler: APIHandler<T>,
options: APIHandlerOptions = {} options: APIHandlerOptions = {}
) { ) {
@@ -394,7 +394,7 @@ export function createPOSTHandler<T = any>(
/** /**
* Utility function for authenticated endpoints * Utility function for authenticated endpoints
*/ */
export function createAuthenticatedHandler<T = any>( export function createAuthenticatedHandler<T = unknown>(
handler: APIHandler<T>, handler: APIHandler<T>,
options: APIHandlerOptions = {} options: APIHandlerOptions = {}
) { ) {
@@ -408,7 +408,7 @@ export function createAuthenticatedHandler<T = any>(
/** /**
* Utility function for admin endpoints * Utility function for admin endpoints
*/ */
export function createAdminHandler<T = any>( export function createAdminHandler<T = unknown>(
handler: APIHandler<T>, handler: APIHandler<T>,
options: APIHandlerOptions = {} options: APIHandlerOptions = {}
) { ) {

View File

@@ -58,10 +58,11 @@ export {
UserRole, UserRole,
} from "./handler"; } from "./handler";
import { createPermissionChecker, type Permission } from "./authorization";
// Re-import types for use in functions below // Re-import types for use in functions below
import type { APIContext, APIHandler, APIHandlerOptions } from "./handler"; import type { APIContext, APIHandler, APIHandlerOptions } from "./handler";
import { createAPIHandler } from "./handler"; import { createAPIHandler } from "./handler";
import { Permission, createPermissionChecker } from "./authorization";
// Response utilities // Response utilities
export { export {
type APIResponse, type APIResponse,

View File

@@ -19,7 +19,7 @@ export interface APIResponseMeta {
version?: string; version?: string;
} }
export interface APIResponse<T = any> { export interface APIResponse<T = unknown> {
success: boolean; success: boolean;
data?: T; data?: T;
error?: string; error?: string;

View File

@@ -47,7 +47,7 @@ export interface CacheStats {
/** /**
* High-performance memory cache with advanced features * High-performance memory cache with advanced features
*/ */
export class PerformanceCache<K extends {} = string, V = any> { export class PerformanceCache<K extends {} = string, V = unknown> {
private cache: LRUCache<K, CacheEntry<V>>; private cache: LRUCache<K, CacheEntry<V>>;
private stats: { private stats: {
hits: number; hits: number;
@@ -245,7 +245,7 @@ class CacheManager {
/** /**
* Create or get a named cache instance * Create or get a named cache instance
*/ */
getCache<K extends {} = string, V = any>( getCache<K extends {} = string, V = unknown>(
name: string, name: string,
options: CacheOptions = {} options: CacheOptions = {}
): PerformanceCache<K, V> { ): PerformanceCache<K, V> {

View File

@@ -13,7 +13,7 @@ import { TIME } from "../constants";
export interface DeduplicationOptions { export interface DeduplicationOptions {
ttl?: number; // How long to keep results cached ttl?: number; // How long to keep results cached
maxPending?: number; // Maximum pending requests per key maxPending?: number; // Maximum pending requests per key
keyGenerator?: (...args: any[]) => string; keyGenerator?: (...args: unknown[]) => string;
timeout?: number; // Request timeout timeout?: number; // Request timeout
} }
@@ -94,7 +94,7 @@ export class RequestDeduplicator {
/** /**
* Memoize a function with deduplication * Memoize a function with deduplication
*/ */
memoize<Args extends any[], Return>( memoize<Args extends readonly unknown[], Return>(
fn: (...args: Args) => Promise<Return>, fn: (...args: Args) => Promise<Return>,
options: DeduplicationOptions = {} options: DeduplicationOptions = {}
) { ) {
@@ -212,7 +212,7 @@ export class RequestDeduplicator {
/** /**
* Generate a key from function arguments * Generate a key from function arguments
*/ */
private generateKey(...args: any[]): string { private generateKey(...args: unknown[]): string {
try { try {
return JSON.stringify(args); return JSON.stringify(args);
} catch { } catch {
@@ -351,7 +351,9 @@ class DeduplicationManager {
ReturnType<RequestDeduplicator["getStats"]> ReturnType<RequestDeduplicator["getStats"]>
> = {}; > = {};
for (const [name, deduplicator] of Array.from(this.deduplicators.entries())) { for (const [name, deduplicator] of Array.from(
this.deduplicators.entries()
)) {
stats[name] = deduplicator.getStats(); stats[name] = deduplicator.getStats();
} }
@@ -427,7 +429,7 @@ export class DeduplicationUtils {
/** /**
* Create a deduplicated version of an async function * Create a deduplicated version of an async function
*/ */
static deduplicate<T extends any[], R>( static deduplicate<T extends readonly unknown[], R>(
fn: (...args: T) => Promise<R>, fn: (...args: T) => Promise<R>,
deduplicatorName = "default", deduplicatorName = "default",
options: DeduplicationOptions = {} options: DeduplicationOptions = {}
@@ -464,7 +466,7 @@ export class DeduplicationUtils {
options options
); );
descriptor.value = function (...args: any[]) { descriptor.value = function (...args: unknown[]) {
const key = `${target.constructor.name}.${propertyKey}:${JSON.stringify(args)}`; const key = `${target.constructor.name}.${propertyKey}:${JSON.stringify(args)}`;
return deduplicator.execute( return deduplicator.execute(
key, key,

View File

@@ -5,10 +5,10 @@
* caching, and deduplication into existing services and API endpoints. * caching, and deduplication into existing services and API endpoints.
*/ */
import { PerformanceUtils, performanceMonitor } from "./monitor";
import { caches, CacheUtils } from "./cache";
import { deduplicators, DeduplicationUtils } from "./deduplication";
import type { NextRequest, NextResponse } from "next/server"; import type { NextRequest, NextResponse } from "next/server";
import { CacheUtils, caches } from "./cache";
import { DeduplicationUtils, deduplicators } from "./deduplication";
import { PerformanceUtils, performanceMonitor } from "./monitor";
/** /**
* Performance integration options * Performance integration options
@@ -235,8 +235,8 @@ export function enhanceAPIRoute(
export function PerformanceEnhanced( export function PerformanceEnhanced(
options: PerformanceIntegrationOptions = {} options: PerformanceIntegrationOptions = {}
) { ) {
return function <T extends new (...args: any[]) => {}>(constructor: T) { return <T extends new (...args: any[]) => {}>(constructor: T) =>
return class extends constructor { class extends constructor {
constructor(...args: any[]) { constructor(...args: any[]) {
super(...args); super(...args);
@@ -259,7 +259,6 @@ export function PerformanceEnhanced(
}); });
} }
}; };
};
} }
/** /**
@@ -268,11 +267,11 @@ export function PerformanceEnhanced(
export function PerformanceOptimized( export function PerformanceOptimized(
options: PerformanceIntegrationOptions = {} options: PerformanceIntegrationOptions = {}
) { ) {
return function ( return (
target: unknown, target: unknown,
propertyKey: string, propertyKey: string,
descriptor: PropertyDescriptor descriptor: PropertyDescriptor
) { ) => {
const originalMethod = descriptor.value; const originalMethod = descriptor.value;
if (typeof originalMethod !== "function") { if (typeof originalMethod !== "function") {
@@ -280,7 +279,7 @@ export function PerformanceOptimized(
} }
descriptor.value = enhanceServiceMethod( descriptor.value = enhanceServiceMethod(
`${(target as any).constructor.name}.${propertyKey}`, `${(target as { constructor: { name: string } }).constructor.name}.${propertyKey}`,
originalMethod, originalMethod,
options options
); );
@@ -293,15 +292,15 @@ export function PerformanceOptimized(
* Simple caching decorator * Simple caching decorator
*/ */
export function Cached( export function Cached(
cacheName: string = "default", cacheName = "default",
ttl: number = 5 * 60 * 1000, ttl: number = 5 * 60 * 1000,
keyGenerator?: (...args: unknown[]) => string keyGenerator?: (...args: unknown[]) => string
) { ) {
return function ( return (
target: unknown, target: unknown,
propertyKey: string, propertyKey: string,
descriptor: PropertyDescriptor descriptor: PropertyDescriptor
) { ) => {
const originalMethod = descriptor.value; const originalMethod = descriptor.value;
if (typeof originalMethod !== "function") { if (typeof originalMethod !== "function") {
@@ -309,14 +308,14 @@ export function Cached(
} }
descriptor.value = CacheUtils.cached( descriptor.value = CacheUtils.cached(
`${(target as any).constructor.name}.${propertyKey}`, `${(target as { constructor: { name: string } }).constructor.name}.${propertyKey}`,
originalMethod, originalMethod,
{ {
ttl, ttl,
keyGenerator: keyGenerator:
keyGenerator || keyGenerator ||
((...args) => ((...args) =>
`${(target as any).constructor.name}.${propertyKey}:${JSON.stringify(args)}`), `${(target as { constructor: { name: string } }).constructor.name}.${propertyKey}:${JSON.stringify(args)}`),
} }
); );
@@ -328,7 +327,7 @@ export function Cached(
* Simple deduplication decorator * Simple deduplication decorator
*/ */
export function Deduplicated( export function Deduplicated(
deduplicatorName: string = "default", deduplicatorName = "default",
ttl: number = 2 * 60 * 1000 ttl: number = 2 * 60 * 1000
) { ) {
return DeduplicationUtils.deduplicatedMethod(deduplicatorName, { ttl }); return DeduplicationUtils.deduplicatedMethod(deduplicatorName, { ttl });
@@ -349,13 +348,16 @@ function mergeOptions(
overrides: PerformanceIntegrationOptions overrides: PerformanceIntegrationOptions
): PerformanceIntegrationOptions { ): PerformanceIntegrationOptions {
return { return {
cache: defaults.cache && overrides.cache cache:
defaults.cache && overrides.cache
? { ...defaults.cache, ...overrides.cache } ? { ...defaults.cache, ...overrides.cache }
: defaults.cache || overrides.cache, : defaults.cache || overrides.cache,
deduplication: defaults.deduplication && overrides.deduplication deduplication:
defaults.deduplication && overrides.deduplication
? { ...defaults.deduplication, ...overrides.deduplication } ? { ...defaults.deduplication, ...overrides.deduplication }
: defaults.deduplication || overrides.deduplication, : defaults.deduplication || overrides.deduplication,
monitoring: defaults.monitoring && overrides.monitoring monitoring:
defaults.monitoring && overrides.monitoring
? { ...defaults.monitoring, ...overrides.monitoring } ? { ...defaults.monitoring, ...overrides.monitoring }
: defaults.monitoring || overrides.monitoring, : defaults.monitoring || overrides.monitoring,
}; };
@@ -367,7 +369,9 @@ function mergeOptions(
export function createEnhancedService<T>( export function createEnhancedService<T>(
ServiceClass: new (...args: unknown[]) => T, ServiceClass: new (...args: unknown[]) => T,
options: PerformanceIntegrationOptions = {} options: PerformanceIntegrationOptions = {}
): new (...args: unknown[]) => T { ): new (
...args: unknown[]
) => T {
return PerformanceEnhanced(options)(ServiceClass as never); return PerformanceEnhanced(options)(ServiceClass as never);
} }
@@ -436,10 +440,7 @@ export function getPerformanceIntegrationStatus() {
* Initialize performance systems * Initialize performance systems
*/ */
export function initializePerformanceSystems( export function initializePerformanceSystems(
options: { options: { monitoring?: boolean; monitoringInterval?: number } = {}
monitoring?: boolean;
monitoringInterval?: number;
} = {}
) { ) {
if (options.monitoring !== false) { if (options.monitoring !== false) {
const interval = options.monitoringInterval || 30000; const interval = options.monitoringInterval || 30000;

View File

@@ -777,7 +777,7 @@ export class PerformanceUtils {
throw new Error("Measured decorator can only be applied to methods"); throw new Error("Measured decorator can only be applied to methods");
} }
descriptor.value = async function (...args: any[]) { descriptor.value = async function (...args: unknown[]) {
const { result, duration } = await PerformanceUtils.measureAsync( const { result, duration } = await PerformanceUtils.measureAsync(
metricName, metricName,
() => originalMethod.apply(this, args) () => originalMethod.apply(this, args)

View File

@@ -5,14 +5,14 @@
* to improve system performance based on real-time metrics. * to improve system performance based on real-time metrics.
*/ */
import {
performanceMonitor,
type PerformanceMetrics,
type Bottleneck,
} from "./monitor";
import { cacheManager, type CacheStats } from "./cache";
import { deduplicationManager } from "./deduplication";
import { TIME } from "../constants"; import { TIME } from "../constants";
import { type CacheStats, cacheManager } from "./cache";
import { deduplicationManager } from "./deduplication";
import {
type Bottleneck,
type PerformanceMetrics,
performanceMonitor,
} from "./monitor";
/** /**
* Optimization action types * Optimization action types
@@ -40,8 +40,8 @@ export interface OptimizationResult {
success: boolean; success: boolean;
message: string; message: string;
metrics?: { metrics?: {
before: any; before: Record<string, unknown>;
after: any; after: Record<string, unknown>;
improvement: number; // Percentage improvement: number; // Percentage
}; };
}; };
@@ -408,7 +408,7 @@ export class PerformanceOptimizer {
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
} else { }
return { return {
action: OptimizationAction.TRIGGER_GARBAGE_COLLECTION, action: OptimizationAction.TRIGGER_GARBAGE_COLLECTION,
target: "system", target: "system",
@@ -419,7 +419,6 @@ export class PerformanceOptimizer {
}, },
timestamp: new Date(), timestamp: new Date(),
}; };
}
} catch (error) { } catch (error) {
return { return {
action: OptimizationAction.TRIGGER_GARBAGE_COLLECTION, action: OptimizationAction.TRIGGER_GARBAGE_COLLECTION,

View File

@@ -346,7 +346,7 @@ export class SecurityAuditLogRepository
if (!acc[key]) { if (!acc[key]) {
acc[key] = { acc[key] = {
userId: event.userId!, userId: event.userId!,
email: event.user?.email || 'Unknown', email: event.user?.email || "Unknown",
count: 0, count: 0,
}; };
} }

View File

@@ -234,7 +234,7 @@ export class UserRepository implements BaseRepository<User> {
data: { data: {
lastLoginAt: new Date(), lastLoginAt: new Date(),
failedLoginAttempts: 0, // Reset failed attempts on successful login failedLoginAttempts: 0, // Reset failed attempts on successful login
lockedAt: null // Unlock account if it was locked lockedAt: null, // Unlock account if it was locked
}, },
}); });
} catch (error) { } catch (error) {
@@ -330,7 +330,9 @@ export class UserRepository implements BaseRepository<User> {
).length; ).length;
const lastActivity = events.length > 0 ? events[0].timestamp : null; const lastActivity = events.length > 0 ? events[0].timestamp : null;
const countriesAccessed = Array.from( const countriesAccessed = Array.from(
new Set(events.map((e) => e.country).filter((c): c is string => c !== null)) new Set(
events.map((e) => e.country).filter((c): c is string => c !== null)
)
); );
return { return {
@@ -406,7 +408,10 @@ export class UserRepository implements BaseRepository<User> {
/** /**
* Increment failed login attempts and lock account if threshold exceeded * Increment failed login attempts and lock account if threshold exceeded
*/ */
async incrementFailedLoginAttempts(email: string, maxAttempts = 5): Promise<User | null> { async incrementFailedLoginAttempts(
email: string,
maxAttempts = 5
): Promise<User | null> {
try { try {
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { email }, where: { email },
@@ -458,7 +463,11 @@ export class UserRepository implements BaseRepository<User> {
/** /**
* Set email verification token * Set email verification token
*/ */
async setEmailVerificationToken(id: string, token: string, expiryHours = 24): Promise<User | null> { async setEmailVerificationToken(
id: string,
token: string,
expiryHours = 24
): Promise<User | null> {
try { try {
const expiry = new Date(Date.now() + expiryHours * 60 * 60 * 1000); const expiry = new Date(Date.now() + expiryHours * 60 * 60 * 1000);
return await prisma.user.update({ return await prisma.user.update({
@@ -519,7 +528,10 @@ export class UserRepository implements BaseRepository<User> {
/** /**
* Update user preferences * Update user preferences
*/ */
async updatePreferences(id: string, preferences: Record<string, unknown>): Promise<User | null> { async updatePreferences(
id: string,
preferences: Record<string, unknown>
): Promise<User | null> {
try { try {
return await prisma.user.update({ return await prisma.user.update({
where: { id }, where: { id },

View File

@@ -242,7 +242,10 @@ class SecurityMonitoringService {
* Configure monitoring thresholds * Configure monitoring thresholds
*/ */
updateConfig(config: DeepPartial<MonitoringConfig>): void { updateConfig(config: DeepPartial<MonitoringConfig>): void {
this.config = this.deepMerge(this.config as any, config as any) as unknown as MonitoringConfig; this.config = this.deepMerge(
this.config as any,
config as any
) as unknown as MonitoringConfig;
} }
/** /**
@@ -260,7 +263,10 @@ class SecurityMonitoringService {
typeof source[key] === "object" && typeof source[key] === "object" &&
!Array.isArray(source[key]) !Array.isArray(source[key])
) { ) {
result[key] = this.deepMerge(target[key] || {} as any, source[key] as any); result[key] = this.deepMerge(
target[key] || ({} as any),
source[key] as any
);
} else { } else {
result[key] = source[key]; result[key] = source[key];
} }

View File

@@ -6,19 +6,19 @@
*/ */
import { import {
PerformanceEnhanced,
PerformanceOptimized,
Cached, Cached,
Deduplicated, Deduplicated,
Monitored, Monitored,
PerformanceEnhanced,
PerformanceOptimized,
} from "../performance/integration"; } from "../performance/integration";
import { AuditOutcome, AuditSeverity } from "../securityAuditLogger";
import { AlertChannel, type MonitoringConfig } from "../securityMonitoring";
import type { Alert, SecurityEvent } from "../types/security";
import { ThreatLevel } from "../types/security";
import { AlertManagementService } from "./AlertManagementService";
import { SecurityEventProcessor } from "./SecurityEventProcessor"; import { SecurityEventProcessor } from "./SecurityEventProcessor";
import { ThreatDetectionService } from "./ThreatDetectionService"; import { ThreatDetectionService } from "./ThreatDetectionService";
import { AlertManagementService } from "./AlertManagementService";
import { AlertChannel, type MonitoringConfig } from "../securityMonitoring";
import { AuditOutcome, AuditSeverity } from "../securityAuditLogger";
import { ThreatLevel } from "../types/security";
import type { SecurityEvent, Alert } from "../types/security";
/** /**
* Configuration for enhanced security service * Configuration for enhanced security service
@@ -170,17 +170,22 @@ export class EnhancedSecurityService {
// Find the highest severity threat // Find the highest severity threat
const highestSeverity = result.threats.reduce((max, threat) => { const highestSeverity = result.threats.reduce((max, threat) => {
const severityOrder = { LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 }; const severityOrder = { LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 };
const current = severityOrder[threat.severity as keyof typeof severityOrder] || 1; const current =
severityOrder[threat.severity as keyof typeof severityOrder] || 1;
const maxVal = severityOrder[max as keyof typeof severityOrder] || 1; const maxVal = severityOrder[max as keyof typeof severityOrder] || 1;
return current > maxVal ? threat.severity : max; return current > maxVal ? threat.severity : max;
}, "LOW" as any); }, "LOW" as any);
// Map AlertSeverity to ThreatLevel // Map AlertSeverity to ThreatLevel
switch (highestSeverity) { switch (highestSeverity) {
case "CRITICAL": return ThreatLevel.CRITICAL; case "CRITICAL":
case "HIGH": return ThreatLevel.HIGH; return ThreatLevel.CRITICAL;
case "MEDIUM": return ThreatLevel.MEDIUM; case "HIGH":
default: return ThreatLevel.LOW; return ThreatLevel.HIGH;
case "MEDIUM":
return ThreatLevel.MEDIUM;
default:
return ThreatLevel.LOW;
} }
} }
@@ -349,7 +354,7 @@ export class EnhancedSecurityService {
// cache: { // cache: {
// enabled: true, // enabled: true,
// ttl: 10 * 60 * 1000, // 10 minutes // ttl: 10 * 60 * 1000, // 10 minutes
// keyGenerator: (query: any) => `search:${JSON.stringify(query)}`, // keyGenerator: (query: Record<string, unknown>) => `search:${JSON.stringify(query)}`,
// }, // },
// deduplication: { // deduplication: {
// enabled: true, // enabled: true,
@@ -398,7 +403,7 @@ export class EnhancedSecurityService {
[ThreatLevel.LOW]: 0, [ThreatLevel.LOW]: 0,
[ThreatLevel.MEDIUM]: 0, [ThreatLevel.MEDIUM]: 0,
[ThreatLevel.HIGH]: 0, [ThreatLevel.HIGH]: 0,
[ThreatLevel.CRITICAL]: 0 [ThreatLevel.CRITICAL]: 0,
}; };
} }
@@ -441,7 +446,9 @@ export class EnhancedSecurityService {
}; };
} }
private async performSearch(query: any): Promise<SecurityEvent[]> { private async performSearch(
query: Record<string, unknown>
): Promise<SecurityEvent[]> {
// Mock search implementation // Mock search implementation
return []; return [];
} }

View File

@@ -68,7 +68,7 @@ export class SecurityMetricsService {
.slice(0, 5); .slice(0, 5);
// User risk scores - transform data to match expected format // User risk scores - transform data to match expected format
const transformedEvents = events.map(event => ({ const transformedEvents = events.map((event) => ({
userId: event.userId || undefined, userId: event.userId || undefined,
user: event.user ? { email: event.user.email } : undefined, user: event.user ? { email: event.user.email } : undefined,
eventType: event.eventType as SecurityEventType, eventType: event.eventType as SecurityEventType,
@@ -76,7 +76,8 @@ export class SecurityMetricsService {
severity: event.severity as AuditSeverity, severity: event.severity as AuditSeverity,
country: event.country || undefined, country: event.country || undefined,
})); }));
const userRiskScores = await this.calculateUserRiskScores(transformedEvents); const userRiskScores =
await this.calculateUserRiskScores(transformedEvents);
// Calculate overall security score // Calculate overall security score
const securityScore = this.calculateSecurityScore({ const securityScore = this.calculateSecurityScore({
@@ -122,7 +123,9 @@ export class SecurityMetricsService {
country?: string; country?: string;
}> }>
): Promise<Array<{ userId: string; email: string; riskScore: number }>> { ): Promise<Array<{ userId: string; email: string; riskScore: number }>> {
const userEvents = events.filter((e) => e.userId) as Array<typeof events[0] & { userId: string }>; const userEvents = events.filter((e) => e.userId) as Array<
(typeof events)[0] & { userId: string }
>;
const userScores = new Map< const userScores = new Map<
string, string,
{ email: string; score: number; events: typeof userEvents } { email: string; score: number; events: typeof userEvents }

View File

@@ -139,7 +139,7 @@ export class ThreatDetectionService {
// Check for geographical anomalies // Check for geographical anomalies
if (context.country && context.userId) { if (context.country && context.userId) {
// Transform historical events to match expected type // Transform historical events to match expected type
const transformedEvents = historicalEvents.map(event => ({ const transformedEvents = historicalEvents.map((event) => ({
userId: event.userId || undefined, userId: event.userId || undefined,
country: event.country || undefined, country: event.country || undefined,
})); }));

View File

@@ -9,8 +9,8 @@
import { initTRPC, TRPCError } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch";
import { getServerSession } from "next-auth/next";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { getServerSession } from "next-auth/next";
import superjson from "superjson"; import superjson from "superjson";
import type { z } from "zod"; import type { z } from "zod";
import { authOptions } from "./auth"; import { authOptions } from "./auth";

View File

@@ -26,7 +26,6 @@ export class BoundedBuffer<T extends { timestamp: Date }> {
* Add item to buffer with automatic cleanup * Add item to buffer with automatic cleanup
*/ */
push(item: T): void { push(item: T): void {
this.buffer.push(item); this.buffer.push(item);
// Trigger cleanup if threshold reached // Trigger cleanup if threshold reached

View File

@@ -55,7 +55,8 @@
"migration:test-trpc": "pnpm exec tsx scripts/migration/trpc-endpoint-tests.ts", "migration:test-trpc": "pnpm exec tsx scripts/migration/trpc-endpoint-tests.ts",
"migration:test-batch": "pnpm exec tsx scripts/migration/batch-processing-tests.ts", "migration:test-batch": "pnpm exec tsx scripts/migration/batch-processing-tests.ts",
"migration:test-all": "pnpm migration:test-trpc && pnpm migration:test-batch && pnpm migration:health-check", "migration:test-all": "pnpm migration:test-trpc && pnpm migration:test-batch && pnpm migration:health-check",
"migration:full": "pnpm migration:pre-check && pnpm migration:backup && pnpm migration:deploy && pnpm migration:health-check" "migration:full": "pnpm migration:pre-check && pnpm migration:backup && pnpm migration:deploy && pnpm migration:health-check",
"prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@prisma/adapter-pg": "^6.11.1", "@prisma/adapter-pg": "^6.11.1",
@@ -152,8 +153,10 @@
"eslint-config-next": "^15.3.5", "eslint-config-next": "^15.3.5",
"eslint-plugin-prettier": "^5.5.1", "eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"husky": "^9.1.7",
"jest-axe": "^10.0.0", "jest-axe": "^10.0.0",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"lint-staged": "^16.1.2",
"markdownlint-cli2": "^0.18.1", "markdownlint-cli2": "^0.18.1",
"node-mocks-http": "^1.17.2", "node-mocks-http": "^1.17.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",
@@ -198,19 +201,17 @@
}, },
"markdownlint-cli2": { "markdownlint-cli2": {
"config": { "config": {
"MD007": { "MD007": false,
"indent": 4,
"start_indented": false,
"start_indent": 4
},
"MD013": false, "MD013": false,
"MD030": { "MD024": false,
"ul_single": 3, "MD029": false,
"ol_single": 2, "MD030": false,
"ul_multi": 3, "MD032": false,
"ol_multi": 2 "MD033": false,
}, "MD036": false,
"MD033": false "MD040": false,
"MD041": false,
"MD046": false
}, },
"ignores": [ "ignores": [
"node_modules", "node_modules",

262
pnpm-lock.yaml generated
View File

@@ -266,10 +266,10 @@ importers:
version: 8.36.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) version: 8.36.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^4.6.0 specifier: ^4.6.0
version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/coverage-v8': '@vitest/coverage-v8':
specifier: ^3.2.4 specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)) version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))
concurrently: concurrently:
specifier: ^9.2.0 specifier: ^9.2.0
version: 9.2.0 version: 9.2.0
@@ -285,12 +285,18 @@ importers:
eslint-plugin-react-hooks: eslint-plugin-react-hooks:
specifier: ^5.2.0 specifier: ^5.2.0
version: 5.2.0(eslint@9.31.0(jiti@2.4.2)) version: 5.2.0(eslint@9.31.0(jiti@2.4.2))
husky:
specifier: ^9.1.7
version: 9.1.7
jest-axe: jest-axe:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0 version: 10.0.0
jsdom: jsdom:
specifier: ^26.1.0 specifier: ^26.1.0
version: 26.1.0 version: 26.1.0
lint-staged:
specifier: ^16.1.2
version: 16.1.2
markdownlint-cli2: markdownlint-cli2:
specifier: ^0.18.1 specifier: ^0.18.1
version: 0.18.1 version: 0.18.1
@@ -326,10 +332,10 @@ importers:
version: 5.8.3 version: 5.8.3
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^5.1.4 specifier: ^5.1.4
version: 5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) version: 5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))
vitest: vitest:
specifier: ^3.2.4 specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3) version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
webpack-bundle-analyzer: webpack-bundle-analyzer:
specifier: ^4.10.2 specifier: ^4.10.2
version: 4.10.2 version: 4.10.2
@@ -2257,6 +2263,10 @@ packages:
ajv@6.12.6: ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-escapes@7.0.0:
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
engines: {node: '>=18'}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2429,6 +2439,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
chalk@5.4.1:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
character-entities-html4@2.1.0: character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
@@ -2452,6 +2466,14 @@ packages:
class-variance-authority@0.7.1: class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
cli-truncate@4.0.0:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
client-only@0.0.1: client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -2481,6 +2503,9 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'} engines: {node: '>=12.5.0'}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -2812,6 +2837,9 @@ packages:
electron-to-chromium@1.5.182: electron-to-chromium@1.5.182:
resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==} resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==}
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
emoji-regex@8.0.0: emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -2830,6 +2858,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
es-abstract@1.24.0: es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3143,6 +3175,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.3.0:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
get-intrinsic@1.3.0: get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3272,6 +3308,11 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
husky@9.1.7:
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
engines: {node: '>=18'}
hasBin: true
i18n-iso-countries@7.14.0: i18n-iso-countries@7.14.0:
resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==} resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
@@ -3373,6 +3414,14 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'} engines: {node: '>=8'}
is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
is-fullwidth-code-point@5.0.0:
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
engines: {node: '>=18'}
is-generator-function@1.1.0: is-generator-function@1.1.0:
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3641,12 +3690,25 @@ packages:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lineclip@1.1.5: lineclip@1.1.5:
resolution: {integrity: sha512-KlA/wRSjpKl7tS9iRUdlG72oQ7qZ1IlVbVgHwoO10TBR/4gQ86uhKow6nlzMAJJhjCWKto8OeoAzzIzKSmN25A==} resolution: {integrity: sha512-KlA/wRSjpKl7tS9iRUdlG72oQ7qZ1IlVbVgHwoO10TBR/4gQ86uhKow6nlzMAJJhjCWKto8OeoAzzIzKSmN25A==}
linkify-it@5.0.0: linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
lint-staged@16.1.2:
resolution: {integrity: sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==}
engines: {node: '>=20.17'}
hasBin: true
listr2@8.3.3:
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
engines: {node: '>=18.0.0'}
locate-path@6.0.0: locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3657,6 +3719,10 @@ packages:
lodash@4.17.21: lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-update@6.1.0:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
longest-streak@3.1.0: longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -3862,6 +3928,10 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
mimic-function@5.0.1:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
min-indent@1.0.1: min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -3916,6 +3986,10 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nano-spawn@1.0.2:
resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==}
engines: {node: '>=20.17'}
nanoid@3.3.11: nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -4048,6 +4122,10 @@ packages:
resolution: {integrity: sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==} resolution: {integrity: sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==}
engines: {node: ^10.13.0 || >=12.0.0} engines: {node: ^10.13.0 || >=12.0.0}
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
opener@1.5.2: opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true hasBin: true
@@ -4159,6 +4237,11 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'} engines: {node: '>=12'}
pidtree@0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
hasBin: true
playwright-core@1.54.1: playwright-core@1.54.1:
resolution: {integrity: sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==} resolution: {integrity: sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -4429,10 +4512,17 @@ packages:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true hasBin: true
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
reusify@1.1.0: reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'} engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rndm@1.2.0: rndm@1.2.0:
resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==}
@@ -4552,6 +4642,14 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
slice-ansi@7.1.0:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'}
sonner@2.0.6: sonner@2.0.6:
resolution: {integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==} resolution: {integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==}
peerDependencies: peerDependencies:
@@ -4586,6 +4684,10 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
string-width@4.2.3: string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4594,6 +4696,10 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'} engines: {node: '>=12'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
string.prototype.includes@2.0.1: string.prototype.includes@2.0.1:
resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5095,6 +5201,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
wrap-ansi@9.0.0:
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
engines: {node: '>=18'}
ws@7.5.10: ws@7.5.10:
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
engines: {node: '>=8.3.0'} engines: {node: '>=8.3.0'}
@@ -5144,6 +5254,11 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'} engines: {node: '>=18'}
yaml@2.8.0:
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1: yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -6884,7 +6999,7 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1': '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true optional: true
'@vitejs/plugin-react@4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3))': '@vitejs/plugin-react@4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies: dependencies:
'@babel/core': 7.28.0 '@babel/core': 7.28.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0)
@@ -6892,11 +7007,11 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.19 '@rolldown/pluginutils': 1.0.0-beta.19
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
react-refresh: 0.17.0 react-refresh: 0.17.0
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3))': '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2 '@bcoe/v8-coverage': 1.0.2
@@ -6911,7 +7026,7 @@ snapshots:
std-env: 3.9.0 std-env: 3.9.0
test-exclude: 7.0.1 test-exclude: 7.0.1
tinyrainbow: 2.0.0 tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3) vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6923,13 +7038,13 @@ snapshots:
chai: 5.2.1 chai: 5.2.1
tinyrainbow: 2.0.0 tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3))': '@vitest/mocker@3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies: dependencies:
'@vitest/spy': 3.2.4 '@vitest/spy': 3.2.4
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.17 magic-string: 0.30.17
optionalDependencies: optionalDependencies:
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/pretty-format@3.2.4': '@vitest/pretty-format@3.2.4':
dependencies: dependencies:
@@ -6981,6 +7096,10 @@ snapshots:
json-schema-traverse: 0.4.1 json-schema-traverse: 0.4.1
uri-js: 4.4.1 uri-js: 4.4.1
ansi-escapes@7.0.0:
dependencies:
environment: 1.1.0
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {} ansi-regex@6.1.0: {}
@@ -7173,6 +7292,8 @@ snapshots:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
supports-color: 7.2.0 supports-color: 7.2.0
chalk@5.4.1: {}
character-entities-html4@2.1.0: {} character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {} character-entities-legacy@3.0.0: {}
@@ -7189,6 +7310,15 @@ snapshots:
dependencies: dependencies:
clsx: 2.1.1 clsx: 2.1.1
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
cli-truncate@4.0.0:
dependencies:
slice-ansi: 5.0.0
string-width: 7.2.0
client-only@0.0.1: {} client-only@0.0.1: {}
cliui@8.0.1: cliui@8.0.1:
@@ -7219,6 +7349,8 @@ snapshots:
color-string: 1.9.1 color-string: 1.9.1
optional: true optional: true
colorette@2.0.20: {}
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@@ -7547,6 +7679,8 @@ snapshots:
electron-to-chromium@1.5.182: {} electron-to-chromium@1.5.182: {}
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
@@ -7560,6 +7694,8 @@ snapshots:
entities@6.0.1: {} entities@6.0.1: {}
environment@1.1.0: {}
es-abstract@1.24.0: es-abstract@1.24.0:
dependencies: dependencies:
array-buffer-byte-length: 1.0.2 array-buffer-byte-length: 1.0.2
@@ -8026,6 +8162,8 @@ snapshots:
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
get-east-asian-width@1.3.0: {}
get-intrinsic@1.3.0: get-intrinsic@1.3.0:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
@@ -8220,6 +8358,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
husky@9.1.7: {}
i18n-iso-countries@7.14.0: i18n-iso-countries@7.14.0:
dependencies: dependencies:
diacritics: 1.3.0 diacritics: 1.3.0
@@ -8317,6 +8457,12 @@ snapshots:
is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@4.0.0: {}
is-fullwidth-code-point@5.0.0:
dependencies:
get-east-asian-width: 1.3.0
is-generator-function@1.1.0: is-generator-function@1.1.0:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -8581,12 +8727,38 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1
lilconfig@3.1.3: {}
lineclip@1.1.5: {} lineclip@1.1.5: {}
linkify-it@5.0.0: linkify-it@5.0.0:
dependencies: dependencies:
uc.micro: 2.1.0 uc.micro: 2.1.0
lint-staged@16.1.2:
dependencies:
chalk: 5.4.1
commander: 14.0.0
debug: 4.4.1
lilconfig: 3.1.3
listr2: 8.3.3
micromatch: 4.0.8
nano-spawn: 1.0.2
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.8.0
transitivePeerDependencies:
- supports-color
listr2@8.3.3:
dependencies:
cli-truncate: 4.0.0
colorette: 2.0.20
eventemitter3: 5.0.1
log-update: 6.1.0
rfdc: 1.4.1
wrap-ansi: 9.0.0
locate-path@6.0.0: locate-path@6.0.0:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
@@ -8595,6 +8767,14 @@ snapshots:
lodash@4.17.21: {} lodash@4.17.21: {}
log-update@6.1.0:
dependencies:
ansi-escapes: 7.0.0
cli-cursor: 5.0.0
slice-ansi: 7.1.0
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
longest-streak@3.1.0: {} longest-streak@3.1.0: {}
loose-envify@1.4.0: loose-envify@1.4.0:
@@ -8968,6 +9148,8 @@ snapshots:
mime@1.6.0: {} mime@1.6.0: {}
mimic-function@5.0.1: {}
min-indent@1.0.1: {} min-indent@1.0.1: {}
minimatch@3.1.2: minimatch@3.1.2:
@@ -9006,6 +9188,8 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
nano-spawn@1.0.2: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}
napi-postinstall@0.3.0: {} napi-postinstall@0.3.0: {}
@@ -9137,6 +9321,10 @@ snapshots:
oidc-token-hash@5.1.0: {} oidc-token-hash@5.1.0: {}
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
opener@1.5.2: {} opener@1.5.2: {}
openid-client@5.7.1: openid-client@5.7.1:
@@ -9249,6 +9437,8 @@ snapshots:
picomatch@4.0.2: {} picomatch@4.0.2: {}
pidtree@0.6.0: {}
playwright-core@1.54.1: {} playwright-core@1.54.1: {}
playwright@1.54.1: playwright@1.54.1:
@@ -9534,8 +9724,15 @@ snapshots:
path-parse: 1.0.7 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
signal-exit: 4.1.0
reusify@1.1.0: {} reusify@1.1.0: {}
rfdc@1.4.1: {}
rndm@1.2.0: {} rndm@1.2.0: {}
robust-predicates@3.0.2: {} robust-predicates@3.0.2: {}
@@ -9716,6 +9913,16 @@ snapshots:
slash@5.1.0: {} slash@5.1.0: {}
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
slice-ansi@7.1.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 5.0.0
sonner@2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): sonner@2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
@@ -9740,6 +9947,8 @@ snapshots:
streamsearch@1.1.0: {} streamsearch@1.1.0: {}
string-argv@0.3.2: {}
string-width@4.2.3: string-width@4.2.3:
dependencies: dependencies:
emoji-regex: 8.0.0 emoji-regex: 8.0.0
@@ -9752,6 +9961,12 @@ snapshots:
emoji-regex: 9.2.2 emoji-regex: 9.2.2
strip-ansi: 7.1.0 strip-ansi: 7.1.0
string-width@7.2.0:
dependencies:
emoji-regex: 10.4.0
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
string.prototype.includes@2.0.1: string.prototype.includes@2.0.1:
dependencies: dependencies:
call-bind: 1.0.8 call-bind: 1.0.8
@@ -10164,13 +10379,13 @@ snapshots:
d3-time: 3.1.0 d3-time: 3.1.0
d3-timer: 3.0.1 d3-timer: 3.0.1
vite-node@3.2.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3): vite-node@3.2.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.4.1 debug: 4.4.1
es-module-lexer: 1.7.0 es-module-lexer: 1.7.0
pathe: 2.0.3 pathe: 2.0.3
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- jiti - jiti
@@ -10185,18 +10400,18 @@ snapshots:
- tsx - tsx
- yaml - yaml
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)): vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)):
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1
globrex: 0.1.2 globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.8.3) tsconfck: 3.1.6(typescript@5.8.3)
optionalDependencies: optionalDependencies:
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3): vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies: dependencies:
esbuild: 0.25.6 esbuild: 0.25.6
fdir: 6.4.6(picomatch@4.0.2) fdir: 6.4.6(picomatch@4.0.2)
@@ -10210,12 +10425,13 @@ snapshots:
jiti: 2.4.2 jiti: 2.4.2
lightningcss: 1.30.1 lightningcss: 1.30.1
tsx: 4.20.3 tsx: 4.20.3
yaml: 2.8.0
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3): vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies: dependencies:
'@types/chai': 5.2.2 '@types/chai': 5.2.2
'@vitest/expect': 3.2.4 '@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) '@vitest/mocker': 3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4 '@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4 '@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4 '@vitest/snapshot': 3.2.4
@@ -10233,8 +10449,8 @@ snapshots:
tinyglobby: 0.2.14 tinyglobby: 0.2.14
tinypool: 1.1.1 tinypool: 1.1.1
tinyrainbow: 2.0.0 tinyrainbow: 2.0.0
vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
vite-node: 3.2.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3) vite-node: 3.2.4(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/debug': 4.1.12 '@types/debug': 4.1.12
@@ -10381,6 +10597,12 @@ snapshots:
string-width: 5.1.2 string-width: 5.1.2
strip-ansi: 7.1.0 strip-ansi: 7.1.0
wrap-ansi@9.0.0:
dependencies:
ansi-styles: 6.2.1
string-width: 7.2.0
strip-ansi: 7.1.0
ws@7.5.10: {} ws@7.5.10: {}
ws@8.18.3: {} ws@8.18.3: {}
@@ -10399,6 +10621,8 @@ snapshots:
yallist@5.0.0: {} yallist@5.0.0: {}
yaml@2.8.0: {}
yargs-parser@21.1.1: {} yargs-parser@21.1.1: {}
yargs@17.7.2: yargs@17.7.2: