Compare commits

..

2 Commits

Author SHA1 Message Date
Luca Sacchi Ricciardi
92217897ca fix: standardize project name to LogWhispererAI (no space)
- Replace all occurrences of 'LogWhisperer AI' with 'LogWhispererAI'
- Fix 47 instances across 30 files including:
  - Documentation (README, PRD, specs, docs)
  - Frontend components (Footer, Navbar, Hero, etc.)
  - Backend files (Dockerfile, server.js)
  - Workflow files (n8n, bash scripts)
  - Configuration files (AGENTS.md, LICENSE)

Ensures consistent branding across the entire codebase.
2026-04-03 17:07:35 +02:00
Luca Sacchi Ricciardi
8eb7dfb00e feat: add fake-backend mock API server for frontend development
Create mock backend to simulate AI responses for UI development:

Backend Implementation:
- tools/fake-backend/server.js: Express server with CORS
- POST /api/analyze: Accepts log, returns mock AI analysis with 1.5s delay
- GET /health: Health check endpoint
- Pattern matching for different log types (PostgreSQL, Nginx, Node.js, Disk)
- Error handling: 400 for empty payload, 500 for server errors
- Mock responses for common errors (OOM, 502, connection refused, disk full)

Container Setup:
- Dockerfile: Node.js 20 Alpine container
- docker-compose.yml: Added fake-backend service on port 3000
- Health checks for both frontend and backend services
- Environment variable VITE_API_URL for frontend

Frontend Integration:
- InteractiveDemo.tsx: Replaced static data with real fetch() calls
- API_URL configurable via env var (default: http://localhost:3000)
- Error handling with user-friendly messages
- Shows backend URL in demo section
- Maintains loading states and UI feedback

Documentation:
- docs/tools_fake_backend.md: Complete usage guide
- README.md: Updated with tools/fake-backend structure and usage

Development Workflow:
1. docker compose up -d (starts both frontend and backend)
2. Frontend calls http://fake-backend:3000/api/analyze
3. Backend returns realistic mock responses
4. No OpenRouter API costs during development

Safety First:
- No real API calls during development
- Isolated mock logic in dedicated tool
- Easy switch to real backend by changing URL
- CORS enabled only for development

Refs: Sprint 4 preparation, API development workflow
2026-04-03 16:57:14 +02:00
34 changed files with 1531 additions and 125 deletions

View File

@@ -237,6 +237,11 @@ LogWhispererAI/
├── tests/
│ ├── __init__.py
│ └── test_logwhisperer.py # Test suite Python
├── tools/
│ └── fake-backend/ # Mock API server per sviluppo frontend
│ ├── server.js # Server Express mock
│ ├── Dockerfile # Containerizzazione
│ └── README.md # Documentazione tool
└── .opencode/
├── opencode.json # Configurazione MCP servers
├── agents/ # Configurazioni agenti individuali
@@ -256,6 +261,31 @@ LogWhispererAI/
└── context7_documentation_retrivial/
```
## 🛠️ Tools di Sviluppo
### Fake Backend (Mock API)
Per sviluppare e testare il frontend senza dipendere dal backend reale:
```bash
# Avvia il mock API server
cd tools/fake-backend
npm install
node server.js
# Oppure con Docker
docker compose up fake-backend -d
```
**Endpoint:** `http://localhost:3000/api/analyze`
Simula le risposte AI con delay di 1.5s. Utile per:
- Sviluppo UI offline
- Testing senza costi API
- Demo senza dipendenze esterne
Vedi `docs/tools_fake_backend.md` per documentazione completa.
## ⚖️ Licenza e Note Legali
Questo software è **proprietà riservata** di Luca Sacchi Ricciardi.

View File

@@ -1,6 +1,7 @@
# Docker Compose - LogWhispererAI Development Environment
# Usage: docker compose up -d
# Access: http://localhost:5173
# Access Frontend: http://localhost:5173
# Access Fake Backend API: http://localhost:3000
services:
frontend:
@@ -18,8 +19,11 @@ services:
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
- VITE_API_URL=http://fake-backend:3000
# Ensure container restarts on failure
restart: unless-stopped
depends_on:
- fake-backend
# Health check to verify the service is running
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173"]
@@ -28,6 +32,25 @@ services:
retries: 3
start_period: 40s
fake-backend:
build:
context: ./tools/fake-backend
dockerfile: Dockerfile
container_name: logwhisperer-fake-backend
ports:
- "3000:3000"
environment:
- PORT=3000
- DELAY_MS=1500
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
volumes:
node_modules:
driver: local

192
docs/tools_fake_backend.md Normal file
View File

@@ -0,0 +1,192 @@
# Fake Backend Tool
> **Strumento di sviluppo per simulare risposte API AI**
>
> Scopo: Permettere lo sviluppo e il testing del frontend senza dipendere dal backend reale o dalle API di OpenRouter.
---
## 📋 Descrizione
Il `fake-backend` è un server Node.js/Express che simula le risposte dell'API di LogWhispererAI. È progettato per:
- **Sviluppo UI**: Testare l'interfaccia utente con risposte realistiche
- **Demo offline**: Mostrare il prodotto senza connessione internet
- **Testing**: Validare il flusso frontend senza costi API
- **Onboarding**: Permettere ai nuovi sviluppatori di lavorare subito
---
## 🚀 Endpoint API
### POST /api/analyze
Analizza un log e restituisce una risposta AI simulata.
**Request:**
```http
POST http://localhost:3000/api/analyze
Content-Type: application/json
{
"log": "FATAL: database system is out of memory"
}
```
**Response (dopo 1.5s delay):**
```json
{
"success": true,
"analysis": {
"title": "PostgreSQL Out of Memory",
"description": "Il database ha esaurito la memoria disponibile...",
"command": "ps aux | grep postgres | head -5 && free -h",
"isSafe": true,
"note": "Verifica processi Postgres e memoria disponibile."
},
"timestamp": "2026-04-03T10:30:00.000Z"
}
```
**Errori gestiti:**
- `400 Bad Request`: Payload vuoto o malformato
- `500 Internal Error`: Errore server generico
---
## 🛠️ Setup
### Prerequisiti
- Node.js 18+
- npm 9+
### Installazione
```bash
# Entra nella directory
cd tools/fake-backend
# Installa dipendenze
npm install
# Avvia il server
node server.js
```
Il server sarà disponibile su `http://localhost:3000`
---
## 🐳 Docker
Per avviare il fake backend con Docker:
```bash
# Dalla root del progetto
docker compose up fake-backend -d
```
Il servizio sarà esposto sulla porta 3000.
---
## 🔧 Configurazione
Variabili ambiente (opzionali):
```bash
PORT=3000 # Porta server (default: 3000)
DELAY_MS=1500 # Delay simulazione AI (default: 1500ms)
```
---
## 📁 Struttura
```
tools/fake-backend/
├── server.js # Server Express principale
├── package.json # Dipendenze npm
├── Dockerfile # Containerizzazione
└── README.md # Questo file
```
---
## 🔒 Sicurezza
⚠️ **ATTENZIONE**: Questo è uno strumento di sviluppo. NON usarlo in produzione:
- Nessuna autenticazione implementata
- Risposte statiche predefinite
- Nessuna validazione input avanzata
- CORS abilitato per tutte le origini
---
## 🧪 Test Manuale
```bash
# Test endpoint analyze
curl -X POST http://localhost:3000/api/analyze \
-H "Content-Type: application/json" \
-d '{"log": "Error: Connection refused"}'
```
---
## 📝 Note per Sviluppatori
### Aggiungere nuove risposte mock
Modifica l'oggetto `MOCK_RESPONSES` in `server.js`:
```javascript
const MOCK_RESPONSES = {
'errore-specifico': {
title: 'Titolo Problema',
description: 'Descrizione...',
command: 'comando-da-eseguire',
isSafe: true,
note: 'Nota aggiuntiva'
}
};
```
### Pattern matching
Il server cerca keyword nel log e restituisce la risposta appropriata:
- "memory" / "oom" → PostgreSQL OOM
- "connection refused" / "502" → Nginx/Connection error
- Default → Risposta generica
---
## 🤝 Integrazione Frontend
Per usare il fake backend dal frontend React:
```typescript
const analyzeLog = async (log: string) => {
const response = await fetch('http://localhost:3000/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ log })
});
return response.json();
};
```
---
## 🔄 Passaggio a Backend Reale
Quando il backend reale è pronto:
1. Aggiorna l'URL in `InteractiveDemo.tsx`
2. Aggiungi autenticazione (JWT/API Key)
3. Rimuovi CORS permissivo
4. Implementa rate limiting
---
*Strumento creato seguendo il principio "Safety First" del Metodo Sacchi*

View File

@@ -1,85 +1,34 @@
import React, { useState } from 'react';
import { Terminal, Copy, Check, Loader2, AlertCircle, Activity } from 'lucide-react';
import { Terminal, Copy, Check, Loader2, AlertCircle, Activity, Server, Globe, Code } from 'lucide-react';
// Mock data for demo logs
// Demo log presets - only the content, not the analysis
const DEMO_LOGS = [
{
id: 'postgres-oom',
label: 'PostgreSQL OOM',
icon: <Database className="w-4 h-4" />,
icon: Server,
logContent: `FATAL: database system is out of memory
DETAIL: Failed on request of size 8192
HINT: Check memory usage and limits
CONTEXT: automatic vacuum of table "public.events"`,
analysis: {
title: 'PostgreSQL Out of Memory',
description: 'Il database ha esaurito la memoria disponibile durante un\'operazione di vacuum automatico su una tabella molto grande.',
command: 'ps aux | grep postgres | head -5 && free -h',
isSafe: true,
note: 'Verifica processi Postgres e memoria disponibile. Se necessario, aumenta work_mem o shared_buffers.',
},
},
{
id: 'nginx-502',
label: 'Nginx 502 Bad Gateway',
icon: <Globe className="w-4 h-4" />,
icon: Globe,
logContent: `2024/01/15 14:32:15 [error] 1234#1234: *56789 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.100, server: api.example.com, upstream: "127.0.0.1:3000"`,
analysis: {
title: 'Nginx 502 - Backend Non Raggiungibile',
description: 'Nginx non riesce a connettersi al backend sulla porta 3000. Probabilmente il servizio è down.',
command: 'sudo systemctl status app-service && netstat -tlnp | grep 3000',
isSafe: true,
note: 'Verifica stato del servizio backend. Se stopped, avvia con: sudo systemctl start app-service',
},
},
{
id: 'node-exception',
label: 'Node.js Exception',
icon: <Code className="w-4 h-4" />,
icon: Code,
logContent: `Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16)
at emitErrorNT (internal/streams/destroy.js:92:8)
at processTicksAndRejections (internal/process/task_queues.js:80:21)`,
analysis: {
title: 'Node.js - Connessione Database Rifiutata',
description: 'L\'applicazione Node non riesce a connettersi al database PostgreSQL sulla porta 5432.',
command: 'sudo systemctl status postgresql && sudo netstat -tlnp | grep 5432',
isSafe: true,
note: 'Verifica che PostgreSQL sia in esecuzione. Se down, avvia con: sudo systemctl start postgresql',
},
},
];
// Icon components
function Database(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M3 5V19A9 3 0 0 0 21 19V5" />
<path d="M3 12A9 3 0 0 0 21 12" />
</svg>
);
}
function Globe(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
);
}
function Code(props: React.SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
);
}
interface LogAnalysis {
title: string;
description: string;
@@ -88,26 +37,60 @@ interface LogAnalysis {
note: string;
}
interface ApiResponse {
success: boolean;
analysis: LogAnalysis & { originalLog?: string };
meta?: {
processingTime: string;
model: string;
timestamp: string;
};
error?: string;
message?: string;
}
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
export const InteractiveDemo: React.FC = () => {
const [selectedLog, setSelectedLog] = useState<string | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [analysis, setAnalysis] = useState<LogAnalysis | null>(null);
const [copied, setCopied] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleLogSelect = async (logId: string) => {
const logData = DEMO_LOGS.find((l) => l.id === logId);
if (!logData) return;
const handleLogSelect = (logId: string) => {
setSelectedLog(logId);
setIsAnalyzing(true);
setAnalysis(null);
setCopied(false);
setError(null);
// Simulate AI analysis delay
setTimeout(() => {
const log = DEMO_LOGS.find((l) => l.id === logId);
if (log) {
setAnalysis(log.analysis);
try {
// Real API call to fake-backend
const response = await fetch(`${API_URL}/api/analyze`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ log: logData.logContent }),
});
const data: ApiResponse = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.message || `HTTP error! status: ${response.status}`);
}
setAnalysis(data.analysis);
} catch (err) {
console.error('API Error:', err);
setError(err instanceof Error ? err.message : 'Errore durante l\'analisi. Verifica che il backend sia attivo.');
} finally {
setIsAnalyzing(false);
}
}, 1500);
};
const handleCopyCommand = () => {
@@ -119,6 +102,7 @@ export const InteractiveDemo: React.FC = () => {
};
const selectedLogData = DEMO_LOGS.find((l) => l.id === selectedLog);
const SelectedIcon = selectedLogData?.icon || Terminal;
return (
<section id="demo-interattiva" className="w-full py-24 lg:py-32 bg-white" aria-labelledby="demo-heading">
@@ -131,6 +115,9 @@ export const InteractiveDemo: React.FC = () => {
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
Seleziona un log di esempio e vedi come l'AI lo trasforma in un comando risolutivo in pochi secondi.
</p>
<p className="text-sm text-slate-500 mt-2">
Backend: <code className="bg-slate-100 px-2 py-1 rounded">{API_URL}</code>
</p>
</div>
{/* Two Column Layout */}
@@ -154,7 +141,9 @@ export const InteractiveDemo: React.FC = () => {
<div className="p-4 border-b border-slate-700">
<p className="text-slate-400 text-sm mb-3">Seleziona un log di esempio:</p>
<div className="flex flex-wrap gap-2">
{DEMO_LOGS.map((log) => (
{DEMO_LOGS.map((log) => {
const IconComponent = log.icon;
return (
<button
key={log.id}
onClick={() => handleLogSelect(log.id)}
@@ -166,10 +155,11 @@ export const InteractiveDemo: React.FC = () => {
} disabled:opacity-50 disabled:cursor-not-allowed`}
aria-pressed={selectedLog === log.id}
>
{log.icon}
<IconComponent className="w-4 h-4" />
{log.label}
</button>
))}
);
})}
</div>
</div>
@@ -197,7 +187,7 @@ export const InteractiveDemo: React.FC = () => {
aria-live="polite"
aria-atomic="true"
>
{!selectedLog && !isAnalyzing && !analysis && (
{!selectedLog && !isAnalyzing && !analysis && !error && (
<div className="h-full flex flex-col items-center justify-center text-center text-slate-400">
<Activity className="w-16 h-16 mb-4 opacity-50" aria-hidden="true" />
<p className="text-lg">L'output dell'analisi apparirà qui</p>
@@ -218,12 +208,24 @@ export const InteractiveDemo: React.FC = () => {
</div>
)}
{analysis && !isAnalyzing && (
{error && !isAnalyzing && (
<div className="h-full flex flex-col items-center justify-center text-center">
<AlertCircle className="w-16 h-16 mb-4 text-red-500" aria-hidden="true" />
<p className="text-lg font-medium text-red-700 mb-2">Errore</p>
<p className="text-sm text-slate-600 mb-4">{error}</p>
<p className="text-xs text-slate-500">
Assicurati che il fake-backend sia in esecuzione:<br />
<code className="bg-slate-200 px-2 py-1 rounded">docker compose up fake-backend -d</code>
</p>
</div>
)}
{analysis && !isAnalyzing && !error && (
<div className="space-y-6">
{/* Analysis Header */}
<div className="flex items-start gap-3">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center flex-shrink-0">
<AlertCircle className="w-5 h-5 text-indigo-600" aria-hidden="true" />
<SelectedIcon className="w-5 h-5 text-indigo-600" aria-hidden="true" />
</div>
<div>
<h3 className="text-xl font-bold text-slate-900">{analysis.title}</h3>

33
tools/fake-backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Logs
logs
*.log
# Environment variables
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,31 @@
# Dockerfile for Fake Backend
# Development mock API server for LogWhispererAI
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy server code
COPY server.js ./
# Expose port 3000
EXPOSE 3000
# Set environment variables
ENV PORT=3000
ENV DELAY_MS=1500
ENV NODE_ENV=production
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
# Start server
CMD ["node", "server.js"]

854
tools/fake-backend/package-lock.json generated Normal file
View File

@@ -0,0 +1,854 @@
{
"name": "fake-backend",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fake-backend",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serve-static": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
"integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

View File

@@ -0,0 +1,17 @@
{
"name": "fake-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1"
}
}

View File

@@ -0,0 +1,224 @@
/**
* Fake Backend Server for LogWhispererAI
*
* Simulates AI analysis responses for frontend development
* without requiring real backend or OpenRouter API calls.
*
* @author LogWhispererAI Team
* @version 1.0.0
*/
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
const DELAY_MS = parseInt(process.env.DELAY_MS) || 1500;
// Enable CORS for all origins (development only!)
app.use(cors({
origin: '*',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type']
}));
// Parse JSON bodies
app.use(express.json());
// Mock responses database
const MOCK_RESPONSES = {
// PostgreSQL errors
'memory': {
title: 'PostgreSQL Out of Memory',
description: 'Il database ha esaurito la memoria disponibile. Questo è spesso causato da query troppo pesanti o da un numero eccessivo di connessioni.',
command: 'ps aux | grep postgres | head -5 && free -h',
isSafe: true,
note: 'Verifica processi Postgres e memoria disponibile. Se necessario, riavvia il servizio con: sudo systemctl restart postgresql'
},
'oom': {
title: 'PostgreSQL Out of Memory',
description: 'Out Of Memory error rilevato. Il sistema ha esaurito la RAM disponibile.',
command: 'free -h && ps aux --sort=-%mem | head -10',
isSafe: true,
note: 'Identifica i processi che consumano più memoria e considera di aumentare la RAM o ottimizzare le query.'
},
// Nginx/Connection errors
'connection refused': {
title: 'Connessione al Backend Rifiutata',
description: 'Il reverse proxy (Nginx) non riesce a connettersi al backend. Il servizio potrebbe essere down.',
command: 'sudo systemctl status app-service && netstat -tlnp | grep 3000',
isSafe: true,
note: 'Verifica che il servizio backend sia in esecuzione. Se stopped, avvia con: sudo systemctl start app-service'
},
'502': {
title: 'Nginx 502 Bad Gateway',
description: 'Nginx riceve errore dal server upstream. Il backend non risponde correttamente.',
command: 'sudo systemctl status backend && tail -n 50 /var/log/backend/error.log',
isSafe: true,
note: 'Controlla i log del backend per errori specifici. Potrebbe essere necessario un riavvio.'
},
// Node.js errors
'econnrefused': {
title: 'Node.js - Connessione Database Rifiutata',
description: 'L\'applicazione Node non riesce a connettersi al database PostgreSQL sulla porta 5432.',
command: 'sudo systemctl status postgresql && sudo netstat -tlnp | grep 5432',
isSafe: true,
note: 'Verifica che PostgreSQL sia in esecuzione. Se down, avvia con: sudo systemctl start postgresql'
},
'exception': {
title: 'Node.js Exception',
description: 'Eccezione non gestita nell\'applicazione Node.js. Potrebbe essere un errore di connessione o configurazione.',
command: 'pm2 logs app --lines 50',
isSafe: true,
note: 'Controlla i log dell\'applicazione per l\'errore completo. Usa pm2 restart app se necessario.'
},
// Disk errors
'disk': {
title: 'Spazio su Disco Esaurito',
description: 'Il filesystem ha raggiunto il 100% di utilizzo. Nessuna scrittura possibile.',
command: 'df -h && du -sh /var/log/* | sort -hr | head -10',
isSafe: true,
note: 'Identifica quali directory occupano più spazio. Pulisci log vecchi con: sudo find /var/log -name "*.log" -mtime +7 -delete'
},
'no space': {
title: 'Spazio su Disco Esaurito',
description: 'Spazio insufficiente sul disco. Impossibile scrivere nuovi dati.',
command: 'df -h && du -sh /tmp /var/log /var/cache',
isSafe: true,
note: 'Libera spazio eliminando file temporanei o log vecchi.'
}
};
// Default response for unknown errors
const DEFAULT_RESPONSE = {
title: 'Errore di Sistema',
description: 'È stato rilevato un errore nel log. L\'analisi suggerisce di verificare lo stato dei servizi e le risorse di sistema.',
command: 'sudo systemctl status && df -h && free -h',
isSafe: true,
note: 'Esegui il comando sopra per verificare lo stato generale del sistema. Se il problema persiste, controlla i log specifici del servizio.'
};
/**
* Find matching mock response based on log content
* @param {string} logContent - The log content to analyze
* @returns {object} - Matching response or default
*/
function findMockResponse(logContent) {
const logLower = logContent.toLowerCase();
for (const [keyword, response] of Object.entries(MOCK_RESPONSES)) {
if (logLower.includes(keyword.toLowerCase())) {
return response;
}
}
return DEFAULT_RESPONSE;
}
/**
* POST /api/analyze
* Analyze log and return AI-like response
*/
app.post('/api/analyze', (req, res) => {
const { log } = req.body;
// Validate input
if (!log || typeof log !== 'string' || log.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Bad Request',
message: 'Il campo "log" è richiesto e non può essere vuoto',
timestamp: new Date().toISOString()
});
}
// Simulate AI processing delay
setTimeout(() => {
try {
const analysis = findMockResponse(log);
res.json({
success: true,
analysis: {
...analysis,
originalLog: log.substring(0, 500) // Include first 500 chars for reference
},
meta: {
processingTime: `${DELAY_MS}ms`,
model: 'fake-backend-mock-v1',
timestamp: new Date().toISOString()
}
});
} catch (error) {
console.error('Error processing request:', error);
res.status(500).json({
success: false,
error: 'Internal Server Error',
message: 'Errore durante l\'analisi del log',
timestamp: new Date().toISOString()
});
}
}, DELAY_MS);
});
/**
* GET /health
* Health check endpoint
*/
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
version: '1.0.0'
});
});
/**
* GET /
* Root endpoint with API info
*/
app.get('/', (req, res) => {
res.json({
name: 'LogWhispererAI - Fake Backend',
version: '1.0.0',
description: 'Mock API server for frontend development',
endpoints: {
'POST /api/analyze': 'Analyze log and return AI-like response',
'GET /health': 'Health check endpoint'
},
documentation: 'See docs/tools_fake_backend.md'
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).json({
success: false,
error: 'Internal Server Error',
message: 'Errore imprevisto del server',
timestamp: new Date().toISOString()
});
});
// Start server
app.listen(PORT, () => {
console.log(`
╔══════════════════════════════════════════════════════════════╗
║ LogWhispererAI - Fake Backend Server ║
║ ║
║ 🚀 Server running on http://localhost:${PORT}
║ 📖 Documentation: docs/tools_fake_backend.md ║
║ ⏱️ Simulated delay: ${DELAY_MS}ms ║
║ ║
║ Endpoints: ║
║ POST /api/analyze - Analyze log and get mock AI response ║
║ GET /health - Health check ║
║ ║
║ Press Ctrl+C to stop ║
╚══════════════════════════════════════════════════════════════╝
`);
});
module.exports = app; // Export for testing