algorembrant commited on
Commit
683d9cb
·
verified ·
1 Parent(s): e1fac76

Upload 26 files

Browse files
README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ```bash
2
+ npm install express mongoose multer cors cheerio body-parser
3
+ ```
STRUCTURE.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Project Structure
2
+
3
+ ```text
4
+ mt5-analyzer/
5
+ ├── client/
6
+ │ ├── public/
7
+ │ │ └── vite.svg
8
+ │ ├── src/
9
+ │ │ ├── assets/
10
+ │ │ │ └── react.svg
11
+ │ │ ├── App.css
12
+ │ │ ├── App.jsx
13
+ │ │ ├── DashBoard.jsx
14
+ │ │ ├── index.css
15
+ │ │ ├── main.jsx
16
+ │ │ ├── RightBar.jsx
17
+ │ │ ├── Sidebar.jsx
18
+ │ │ └── UploadModal.jsx
19
+ │ ├── .gitignore
20
+ │ ├── eslint.config.js
21
+ │ ├── index.html
22
+ │ ├── package-lock.json
23
+ │ ├── package.json
24
+ │ ├── README.md
25
+ │ └── vite.config.js
26
+ ├── server/
27
+ │ ├── models/
28
+ │ │ └── Report.js
29
+ │ ├── uploads/
30
+ │ ├── utils/
31
+ │ │ └── parser.js
32
+ │ └── server.js
33
+ ├── package-lock.json
34
+ ├── package.json
35
+ ├── py.py
36
+ ├── README.md
37
+ └── TECHSTACK.md
38
+ ```
TECHSTACK.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Techstack
2
+
3
+ Audit of **mt5-analyzer** project files (excluding environment and cache):
4
+
5
+ | File Type | Count | Size (KB) |
6
+ | :--- | :--- | :--- |
7
+ | JSX (React) (.jsx) | 6 | 14.3 |
8
+ | JavaScript (.js) | 5 | 5.0 |
9
+ | JSON (.json) | 4 | 152.6 |
10
+ | CSS (.css) | 2 | 1.7 |
11
+ | Markdown (.md) | 2 | 1.2 |
12
+ | SVG Image (.svg) | 2 | 5.5 |
13
+ | (no extension) | 1 | 0.2 |
14
+ | HTML (.html) | 1 | 0.3 |
15
+ | Python (.py) | 1 | 8.0 |
16
+ | **Total** | **24** | **188.9** |
client/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
client/README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
client/eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist']),
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ reactHooks.configs.flat.recommended,
14
+ reactRefresh.configs.vite,
15
+ ],
16
+ languageOptions: {
17
+ ecmaVersion: 2020,
18
+ globals: globals.browser,
19
+ parserOptions: {
20
+ ecmaVersion: 'latest',
21
+ ecmaFeatures: { jsx: true },
22
+ sourceType: 'module',
23
+ },
24
+ },
25
+ rules: {
26
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27
+ },
28
+ },
29
+ ])
client/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>client</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
client/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
client/package.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "client",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^19.2.0",
14
+ "react-dom": "^19.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@eslint/js": "^9.39.1",
18
+ "@types/react": "^19.2.5",
19
+ "@types/react-dom": "^19.2.3",
20
+ "@vitejs/plugin-react": "^5.1.1",
21
+ "eslint": "^9.39.1",
22
+ "eslint-plugin-react-hooks": "^7.0.1",
23
+ "eslint-plugin-react-refresh": "^0.4.24",
24
+ "globals": "^16.5.0",
25
+ "vite": "^7.2.4"
26
+ }
27
+ }
client/public/vite.svg ADDED
client/src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
client/src/App.jsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import axios from 'axios';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { FaHome, FaChartLine, FaFolderOpen, FaCog, FaFeatherAlt, FaFileUpload } from 'react-icons/fa';
5
+ import { BsThreeDots } from 'react-icons/bs';
6
+ import Dashboard from './components/Dashboard';
7
+ import Sidebar from './components/Sidebar';
8
+ import RightBar from './components/RightBar';
9
+ import UploadModal from './components/UploadModal';
10
+
11
+ const App = () => {
12
+ const [reports, setReports] = useState([]);
13
+ const [activeReport, setActiveReport] = useState(null);
14
+ const [isUploadOpen, setIsUploadOpen] = useState(false);
15
+
16
+ useEffect(() => {
17
+ fetchReports();
18
+ }, []);
19
+
20
+ const fetchReports = async () => {
21
+ const res = await axios.get('http://localhost:5000/api/reports');
22
+ setReports(res.data);
23
+ };
24
+
25
+ return (
26
+ <div className="flex min-h-screen bg-black text-white font-sans overflow-hidden">
27
+ {/* Left Sidebar (Nav) */}
28
+ <Sidebar onOpenUpload={() => setIsUploadOpen(true)} />
29
+
30
+ {/* Main Content Area */}
31
+ <main className="flex-1 border-x border-gray-800 relative overflow-y-auto">
32
+ <header className="sticky top-0 z-10 bg-black/80 backdrop-blur-md p-4 border-b border-gray-800">
33
+ <h2 className="text-xl font-bold">Home</h2>
34
+ </header>
35
+
36
+ <div className="p-4">
37
+ {activeReport ? (
38
+ <Dashboard report={activeReport} onClose={() => setActiveReport(null)} />
39
+ ) : (
40
+ <div className="text-center mt-20 text-gray-500">
41
+ <p>Select a report from the right sidebar or upload a new one.</p>
42
+ </div>
43
+ )}
44
+ </div>
45
+ </main>
46
+
47
+ {/* Right Sidebar (Stored Files) */}
48
+ <RightBar
49
+ reports={reports}
50
+ onSelect={(r) => setActiveReport(r)}
51
+ />
52
+
53
+ {/* Upload Modal */}
54
+ <AnimatePresence>
55
+ {isUploadOpen && (
56
+ <UploadModal
57
+ close={() => setIsUploadOpen(false)}
58
+ refresh={fetchReports}
59
+ />
60
+ )}
61
+ </AnimatePresence>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ export default App;
client/src/DashBoard.jsx ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useRef, useState, useEffect } from 'react';
2
+ import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts';
3
+ import Draggable from 'react-draggable';
4
+ import { FaPen, FaEraser, FaSave, FaTimes } from 'react-icons/fa';
5
+ import html2canvas from 'html2canvas';
6
+
7
+ const Dashboard = ({ report, onClose }) => {
8
+ const [isDrawingMode, setIsDrawingMode] = useState(false);
9
+ const canvasRef = useRef(null);
10
+ const containerRef = useRef(null);
11
+ const [context, setContext] = useState(null);
12
+
13
+ // Prepare Data for Chart
14
+ const chartData = report.deals.map((deal, index) => ({
15
+ name: index, // Simplified X axis (Trade #)
16
+ balance: deal.balance,
17
+ profit: deal.profit
18
+ }));
19
+
20
+ // Setup Canvas for drawing
21
+ useEffect(() => {
22
+ if (canvasRef.current) {
23
+ const canvas = canvasRef.current;
24
+ canvas.width = canvas.offsetWidth;
25
+ canvas.height = canvas.offsetHeight;
26
+ const ctx = canvas.getContext('2d');
27
+ ctx.strokeStyle = "yellow";
28
+ ctx.lineWidth = 3;
29
+ setContext(ctx);
30
+ }
31
+ }, [isDrawingMode]);
32
+
33
+ const startDrawing = (e) => {
34
+ if (!isDrawingMode || !context) return;
35
+ context.beginPath();
36
+ context.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
37
+ };
38
+
39
+ const draw = (e) => {
40
+ if (!isDrawingMode || !context || e.buttons !== 1) return;
41
+ context.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
42
+ context.stroke();
43
+ };
44
+
45
+ const saveReport = () => {
46
+ if (containerRef.current) {
47
+ html2canvas(containerRef.current).then(canvas => {
48
+ const link = document.createElement('a');
49
+ link.download = `Remodeled_Report_${report.accountId}.png`;
50
+ link.href = canvas.toDataURL();
51
+ link.click();
52
+ });
53
+ }
54
+ };
55
+
56
+ return (
57
+ <Draggable handle=".drag-handle">
58
+ <div
59
+ ref={containerRef}
60
+ className="bg-black border border-gray-700 rounded-xl overflow-hidden relative shadow-2xl min-h-[600px]"
61
+ >
62
+ {/* Header / Drag Handle */}
63
+ <div className="drag-handle bg-gray-900 p-3 cursor-grab flex justify-between items-center border-b border-gray-800">
64
+ <h3 className="font-bold text-white">Account: {report.accountId}</h3>
65
+ <div className="flex gap-2">
66
+ <button onClick={() => setIsDrawingMode(!isDrawingMode)} className={`p-2 rounded ${isDrawingMode ? 'bg-blue-600' : 'bg-gray-700'}`}><FaPen /></button>
67
+ <button onClick={saveReport} className="p-2 bg-green-600 rounded"><FaSave /></button>
68
+ <button onClick={onClose} className="p-2 bg-red-600 rounded"><FaTimes /></button>
69
+ </div>
70
+ </div>
71
+
72
+ {/* Content */}
73
+ <div className="p-4 relative">
74
+
75
+ {/* Canvas Overlay */}
76
+ <canvas
77
+ ref={canvasRef}
78
+ onMouseDown={startDrawing}
79
+ onMouseMove={draw}
80
+ className={`absolute inset-0 z-20 ${isDrawingMode ? 'cursor-crosshair pointer-events-auto' : 'pointer-events-none'}`}
81
+ style={{ width: '100%', height: '100%' }}
82
+ />
83
+
84
+ {/* Grid Layout for Remodeled Report */}
85
+ <div className="grid grid-cols-3 gap-4 mb-6">
86
+ <div className="col-span-3 bg-gray-900/50 p-4 rounded-lg">
87
+ <h4 className="text-gray-400 mb-2">Equity & Balance Curve</h4>
88
+ <div className="h-64 w-full">
89
+ <ResponsiveContainer width="100%" height="100%">
90
+ <AreaChart data={chartData}>
91
+ <defs>
92
+ <linearGradient id="colorBal" x1="0" y1="0" x2="0" y2="1">
93
+ <stop offset="5%" stopColor="#8884d8" stopOpacity={0.8}/>
94
+ <stop offset="95%" stopColor="#8884d8" stopOpacity={0}/>
95
+ </linearGradient>
96
+ </defs>
97
+ <CartesianGrid strokeDasharray="3 3" stroke="#333" />
98
+ <XAxis dataKey="name" stroke="#666" />
99
+ <YAxis stroke="#666" domain={['auto', 'auto']} />
100
+ <Tooltip contentStyle={{backgroundColor: '#000', borderColor: '#333'}} />
101
+ <Area type="monotone" dataKey="balance" stroke="#8884d8" fillOpacity={1} fill="url(#colorBal)" />
102
+ </AreaChart>
103
+ </ResponsiveContainer>
104
+ </div>
105
+ </div>
106
+
107
+ {/* Stat Cards */}
108
+ <div className="bg-gray-800 p-4 rounded text-center">
109
+ <p className="text-gray-400 text-xs">Total Deals</p>
110
+ <p className="text-2xl font-bold">{report.deals.length}</p>
111
+ </div>
112
+ <div className="bg-gray-800 p-4 rounded text-center">
113
+ <p className="text-gray-400 text-xs">Final Balance</p>
114
+ <p className="text-2xl font-bold text-green-400">
115
+ ${report.deals[report.deals.length - 1]?.balance.toFixed(2)}
116
+ </p>
117
+ </div>
118
+ <div className="bg-gray-800 p-4 rounded text-center">
119
+ <p className="text-gray-400 text-xs">Profit Factor</p>
120
+ <p className="text-2xl font-bold">1.5</p> {/* Placeholder calculation */}
121
+ </div>
122
+ </div>
123
+
124
+ {/* Data Table */}
125
+ <div className="overflow-x-auto">
126
+ <table className="w-full text-left text-xs text-gray-400">
127
+ <thead className="bg-gray-900 text-gray-200">
128
+ <tr>
129
+ <th className="p-2">Time</th>
130
+ <th className="p-2">Symbol</th>
131
+ <th className="p-2">Type</th>
132
+ <th className="p-2">Profit</th>
133
+ </tr>
134
+ </thead>
135
+ <tbody>
136
+ {report.deals.slice(0, 10).map((d, i) => (
137
+ <tr key={i} className="border-b border-gray-800 hover:bg-gray-900">
138
+ <td className="p-2">{d.time}</td>
139
+ <td className="p-2 text-blue-400">{d.symbol}</td>
140
+ <td className="p-2">{d.type}</td>
141
+ <td className={`p-2 ${d.profit >= 0 ? 'text-green-500' : 'text-red-500'}`}>
142
+ {d.profit}
143
+ </td>
144
+ </tr>
145
+ ))}
146
+ </tbody>
147
+ </table>
148
+ </div>
149
+
150
+ </div>
151
+ </div>
152
+ </Draggable>
153
+ );
154
+ };
155
+
156
+ export default Dashboard;
client/src/RightBar.jsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { FaFileAlt } from 'react-icons/fa';
3
+
4
+ const RightBar = ({ reports, onSelect }) => {
5
+ return (
6
+ <div className="hidden lg:block w-80 p-4 border-l border-gray-800 h-screen sticky top-0 overflow-y-auto">
7
+ <div className="bg-gray-900 rounded-2xl p-4 mb-4">
8
+ <h2 className="font-bold text-xl mb-4">Storage</h2>
9
+ <div className="space-y-4">
10
+ {reports.map((report) => (
11
+ <div
12
+ key={report._id}
13
+ draggable
14
+ onDragEnd={() => onSelect(report)} // Simple Drag Logic: Drop anywhere to select
15
+ onClick={() => onSelect(report)}
16
+ className="flex items-center gap-3 p-3 hover:bg-gray-800 rounded-xl cursor-grab active:cursor-grabbing transition"
17
+ >
18
+ <div className="bg-blue-500/20 text-blue-500 p-3 rounded-lg">
19
+ <FaFileAlt />
20
+ </div>
21
+ <div className="overflow-hidden">
22
+ <p className="font-bold truncate">Acct: {report.accountId}</p>
23
+ <p className="text-sm text-gray-500">{new Date(report.uploadDate).toLocaleDateString()}</p>
24
+ </div>
25
+ </div>
26
+ ))}
27
+ {reports.length === 0 && <p className="text-gray-500">No reports found.</p>}
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export default RightBar;
client/src/Sidebar.jsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { FaTwitter, FaHome, FaHashtag, FaEnvelope, FaBookmark, FaUser, FaPlusCircle } from 'react-icons/fa';
3
+
4
+ const NavItem = ({ icon, text, active, onClick }) => (
5
+ <div
6
+ onClick={onClick}
7
+ className={`flex items-center gap-4 p-3 rounded-full cursor-pointer transition-colors ${active ? 'font-bold' : 'hover:bg-gray-900'}`}
8
+ >
9
+ <span className="text-2xl">{icon}</span>
10
+ <span className="text-xl hidden xl:block">{text}</span>
11
+ </div>
12
+ );
13
+
14
+ const Sidebar = ({ onOpenUpload }) => {
15
+ return (
16
+ <div className="w-20 xl:w-64 p-4 flex flex-col justify-between h-screen sticky top-0">
17
+ <div className="space-y-4">
18
+ <div className="p-3 text-3xl w-fit rounded-full hover:bg-gray-900 cursor-pointer">
19
+ {/* Logo placeholder - using generic shape */}
20
+ <div className="w-8 h-8 bg-white text-black flex items-center justify-center font-bold rounded-sm">X</div>
21
+ </div>
22
+
23
+ <nav className="space-y-2">
24
+ <NavItem icon={<FaHome />} text="Home" active />
25
+ <NavItem icon={<FaHashtag />} text="Explore" />
26
+ <NavItem icon={<FaBookmark />} text="Saved Reports" />
27
+ <NavItem icon={<FaUser />} text="Profile" />
28
+ </nav>
29
+
30
+ <button
31
+ onClick={onOpenUpload}
32
+ className="w-full bg-blue-500 text-white font-bold py-3 rounded-full mt-4 hover:bg-blue-600 transition shadow-lg flex justify-center items-center gap-2"
33
+ >
34
+ <FaPlusCircle /> <span className="hidden xl:block">Upload MT5</span>
35
+ </button>
36
+ </div>
37
+
38
+ <div className="flex items-center gap-3 p-3 rounded-full hover:bg-gray-900 cursor-pointer">
39
+ <div className="w-10 h-10 bg-gray-700 rounded-full"></div>
40
+ <div className="hidden xl:block">
41
+ <p className="font-bold">Trader User</p>
42
+ <p className="text-gray-500">@trader_1</p>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default Sidebar;
client/src/UploadModal.jsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useCallback } from 'react';
2
+ import { useDropzone } from 'react-dropzone';
3
+ import { motion } from 'framer-motion';
4
+ import axios from 'axios';
5
+ import { FaTimes } from 'react-icons/fa';
6
+
7
+ const UploadModal = ({ close, refresh }) => {
8
+ const onDrop = useCallback(async (acceptedFiles) => {
9
+ const formData = new FormData();
10
+
11
+ // Separate HTML and Images
12
+ acceptedFiles.forEach(file => {
13
+ if (file.name.endsWith('.html')) {
14
+ formData.append('htmlFile', file);
15
+ } else if (file.name.endsWith('.png')) {
16
+ formData.append('images', file);
17
+ }
18
+ });
19
+
20
+ try {
21
+ await axios.post('http://localhost:5000/api/upload', formData, {
22
+ headers: { 'Content-Type': 'multipart/form-data' }
23
+ });
24
+ refresh();
25
+ close();
26
+ } catch (err) {
27
+ alert("Error uploading files");
28
+ }
29
+ }, [close, refresh]);
30
+
31
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
32
+
33
+ return (
34
+ <div className="fixed inset-0 bg-blue-900/20 backdrop-blur-sm flex items-center justify-center z-50">
35
+ <motion.div
36
+ initial={{ opacity: 0, scale: 0.9 }}
37
+ animate={{ opacity: 1, scale: 1 }}
38
+ exit={{ opacity: 0, scale: 0.9 }}
39
+ className="bg-black border border-gray-700 w-[600px] h-[400px] rounded-2xl p-4 flex flex-col relative"
40
+ >
41
+ <button onClick={close} className="absolute top-4 left-4 text-white hover:bg-gray-800 p-2 rounded-full">
42
+ <FaTimes />
43
+ </button>
44
+
45
+ <div className="flex-1 flex flex-col items-center justify-center mt-8">
46
+ <div {...getRootProps()} className={`border-2 border-dashed border-gray-600 rounded-xl w-full h-64 flex items-center justify-center cursor-pointer transition ${isDragActive ? 'bg-gray-900 border-blue-500' : ''}`}>
47
+ <input {...getInputProps()} />
48
+ <div className="text-center">
49
+ <p className="text-blue-400 font-bold text-lg">Drop MT5 HTML & Images here</p>
50
+ <p className="text-gray-500 text-sm">ReportTester-ID.html + 4 pngs</p>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </motion.div>
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export default UploadModal;
client/src/assets/react.svg ADDED
client/src/index.css ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ a {
17
+ font-weight: 500;
18
+ color: #646cff;
19
+ text-decoration: inherit;
20
+ }
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ display: flex;
28
+ place-items: center;
29
+ min-width: 320px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 3.2em;
35
+ line-height: 1.1;
36
+ }
37
+
38
+ button {
39
+ border-radius: 8px;
40
+ border: 1px solid transparent;
41
+ padding: 0.6em 1.2em;
42
+ font-size: 1em;
43
+ font-weight: 500;
44
+ font-family: inherit;
45
+ background-color: #1a1a1a;
46
+ cursor: pointer;
47
+ transition: border-color 0.25s;
48
+ }
49
+ button:hover {
50
+ border-color: #646cff;
51
+ }
52
+ button:focus,
53
+ button:focus-visible {
54
+ outline: 4px auto -webkit-focus-ring-color;
55
+ }
56
+
57
+ @media (prefers-color-scheme: light) {
58
+ :root {
59
+ color: #213547;
60
+ background-color: #ffffff;
61
+ }
62
+ a:hover {
63
+ color: #747bff;
64
+ }
65
+ button {
66
+ background-color: #f9f9f9;
67
+ }
68
+ }
client/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
client/vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })
package-lock.json ADDED
@@ -0,0 +1,1549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mt5-analyzer",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "body-parser": "^2.2.2",
9
+ "cheerio": "^1.1.2",
10
+ "cors": "^2.8.5",
11
+ "express": "^5.2.1",
12
+ "mongoose": "^9.1.2",
13
+ "multer": "^2.0.2"
14
+ }
15
+ },
16
+ "node_modules/@mongodb-js/saslprep": {
17
+ "version": "1.4.4",
18
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz",
19
+ "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "sparse-bitfield": "^3.0.3"
23
+ }
24
+ },
25
+ "node_modules/@types/webidl-conversions": {
26
+ "version": "7.0.3",
27
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
28
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
29
+ "license": "MIT"
30
+ },
31
+ "node_modules/@types/whatwg-url": {
32
+ "version": "13.0.0",
33
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
34
+ "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@types/webidl-conversions": "*"
38
+ }
39
+ },
40
+ "node_modules/accepts": {
41
+ "version": "2.0.0",
42
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
43
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "mime-types": "^3.0.0",
47
+ "negotiator": "^1.0.0"
48
+ },
49
+ "engines": {
50
+ "node": ">= 0.6"
51
+ }
52
+ },
53
+ "node_modules/append-field": {
54
+ "version": "1.0.0",
55
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
56
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
57
+ "license": "MIT"
58
+ },
59
+ "node_modules/body-parser": {
60
+ "version": "2.2.2",
61
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
62
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
63
+ "license": "MIT",
64
+ "dependencies": {
65
+ "bytes": "^3.1.2",
66
+ "content-type": "^1.0.5",
67
+ "debug": "^4.4.3",
68
+ "http-errors": "^2.0.0",
69
+ "iconv-lite": "^0.7.0",
70
+ "on-finished": "^2.4.1",
71
+ "qs": "^6.14.1",
72
+ "raw-body": "^3.0.1",
73
+ "type-is": "^2.0.1"
74
+ },
75
+ "engines": {
76
+ "node": ">=18"
77
+ },
78
+ "funding": {
79
+ "type": "opencollective",
80
+ "url": "https://opencollective.com/express"
81
+ }
82
+ },
83
+ "node_modules/boolbase": {
84
+ "version": "1.0.0",
85
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
86
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
87
+ "license": "ISC"
88
+ },
89
+ "node_modules/bson": {
90
+ "version": "7.0.0",
91
+ "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz",
92
+ "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==",
93
+ "license": "Apache-2.0",
94
+ "engines": {
95
+ "node": ">=20.19.0"
96
+ }
97
+ },
98
+ "node_modules/buffer-from": {
99
+ "version": "1.1.2",
100
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
101
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
102
+ "license": "MIT"
103
+ },
104
+ "node_modules/busboy": {
105
+ "version": "1.6.0",
106
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
107
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
108
+ "dependencies": {
109
+ "streamsearch": "^1.1.0"
110
+ },
111
+ "engines": {
112
+ "node": ">=10.16.0"
113
+ }
114
+ },
115
+ "node_modules/bytes": {
116
+ "version": "3.1.2",
117
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
118
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
119
+ "license": "MIT",
120
+ "engines": {
121
+ "node": ">= 0.8"
122
+ }
123
+ },
124
+ "node_modules/call-bind-apply-helpers": {
125
+ "version": "1.0.2",
126
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
127
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
128
+ "license": "MIT",
129
+ "dependencies": {
130
+ "es-errors": "^1.3.0",
131
+ "function-bind": "^1.1.2"
132
+ },
133
+ "engines": {
134
+ "node": ">= 0.4"
135
+ }
136
+ },
137
+ "node_modules/call-bound": {
138
+ "version": "1.0.4",
139
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
140
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
141
+ "license": "MIT",
142
+ "dependencies": {
143
+ "call-bind-apply-helpers": "^1.0.2",
144
+ "get-intrinsic": "^1.3.0"
145
+ },
146
+ "engines": {
147
+ "node": ">= 0.4"
148
+ },
149
+ "funding": {
150
+ "url": "https://github.com/sponsors/ljharb"
151
+ }
152
+ },
153
+ "node_modules/cheerio": {
154
+ "version": "1.1.2",
155
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
156
+ "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
157
+ "license": "MIT",
158
+ "dependencies": {
159
+ "cheerio-select": "^2.1.0",
160
+ "dom-serializer": "^2.0.0",
161
+ "domhandler": "^5.0.3",
162
+ "domutils": "^3.2.2",
163
+ "encoding-sniffer": "^0.2.1",
164
+ "htmlparser2": "^10.0.0",
165
+ "parse5": "^7.3.0",
166
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
167
+ "parse5-parser-stream": "^7.1.2",
168
+ "undici": "^7.12.0",
169
+ "whatwg-mimetype": "^4.0.0"
170
+ },
171
+ "engines": {
172
+ "node": ">=20.18.1"
173
+ },
174
+ "funding": {
175
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
176
+ }
177
+ },
178
+ "node_modules/cheerio-select": {
179
+ "version": "2.1.0",
180
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
181
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
182
+ "license": "BSD-2-Clause",
183
+ "dependencies": {
184
+ "boolbase": "^1.0.0",
185
+ "css-select": "^5.1.0",
186
+ "css-what": "^6.1.0",
187
+ "domelementtype": "^2.3.0",
188
+ "domhandler": "^5.0.3",
189
+ "domutils": "^3.0.1"
190
+ },
191
+ "funding": {
192
+ "url": "https://github.com/sponsors/fb55"
193
+ }
194
+ },
195
+ "node_modules/concat-stream": {
196
+ "version": "2.0.0",
197
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
198
+ "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
199
+ "engines": [
200
+ "node >= 6.0"
201
+ ],
202
+ "license": "MIT",
203
+ "dependencies": {
204
+ "buffer-from": "^1.0.0",
205
+ "inherits": "^2.0.3",
206
+ "readable-stream": "^3.0.2",
207
+ "typedarray": "^0.0.6"
208
+ }
209
+ },
210
+ "node_modules/content-disposition": {
211
+ "version": "1.0.1",
212
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
213
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
214
+ "license": "MIT",
215
+ "engines": {
216
+ "node": ">=18"
217
+ },
218
+ "funding": {
219
+ "type": "opencollective",
220
+ "url": "https://opencollective.com/express"
221
+ }
222
+ },
223
+ "node_modules/content-type": {
224
+ "version": "1.0.5",
225
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
226
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
227
+ "license": "MIT",
228
+ "engines": {
229
+ "node": ">= 0.6"
230
+ }
231
+ },
232
+ "node_modules/cookie": {
233
+ "version": "0.7.2",
234
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
235
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
236
+ "license": "MIT",
237
+ "engines": {
238
+ "node": ">= 0.6"
239
+ }
240
+ },
241
+ "node_modules/cookie-signature": {
242
+ "version": "1.2.2",
243
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
244
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
245
+ "license": "MIT",
246
+ "engines": {
247
+ "node": ">=6.6.0"
248
+ }
249
+ },
250
+ "node_modules/cors": {
251
+ "version": "2.8.5",
252
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
253
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
254
+ "license": "MIT",
255
+ "dependencies": {
256
+ "object-assign": "^4",
257
+ "vary": "^1"
258
+ },
259
+ "engines": {
260
+ "node": ">= 0.10"
261
+ }
262
+ },
263
+ "node_modules/css-select": {
264
+ "version": "5.2.2",
265
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
266
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
267
+ "license": "BSD-2-Clause",
268
+ "dependencies": {
269
+ "boolbase": "^1.0.0",
270
+ "css-what": "^6.1.0",
271
+ "domhandler": "^5.0.2",
272
+ "domutils": "^3.0.1",
273
+ "nth-check": "^2.0.1"
274
+ },
275
+ "funding": {
276
+ "url": "https://github.com/sponsors/fb55"
277
+ }
278
+ },
279
+ "node_modules/css-what": {
280
+ "version": "6.2.2",
281
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
282
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
283
+ "license": "BSD-2-Clause",
284
+ "engines": {
285
+ "node": ">= 6"
286
+ },
287
+ "funding": {
288
+ "url": "https://github.com/sponsors/fb55"
289
+ }
290
+ },
291
+ "node_modules/debug": {
292
+ "version": "4.4.3",
293
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
294
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
295
+ "license": "MIT",
296
+ "dependencies": {
297
+ "ms": "^2.1.3"
298
+ },
299
+ "engines": {
300
+ "node": ">=6.0"
301
+ },
302
+ "peerDependenciesMeta": {
303
+ "supports-color": {
304
+ "optional": true
305
+ }
306
+ }
307
+ },
308
+ "node_modules/depd": {
309
+ "version": "2.0.0",
310
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
311
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
312
+ "license": "MIT",
313
+ "engines": {
314
+ "node": ">= 0.8"
315
+ }
316
+ },
317
+ "node_modules/dom-serializer": {
318
+ "version": "2.0.0",
319
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
320
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
321
+ "license": "MIT",
322
+ "dependencies": {
323
+ "domelementtype": "^2.3.0",
324
+ "domhandler": "^5.0.2",
325
+ "entities": "^4.2.0"
326
+ },
327
+ "funding": {
328
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
329
+ }
330
+ },
331
+ "node_modules/domelementtype": {
332
+ "version": "2.3.0",
333
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
334
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
335
+ "funding": [
336
+ {
337
+ "type": "github",
338
+ "url": "https://github.com/sponsors/fb55"
339
+ }
340
+ ],
341
+ "license": "BSD-2-Clause"
342
+ },
343
+ "node_modules/domhandler": {
344
+ "version": "5.0.3",
345
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
346
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
347
+ "license": "BSD-2-Clause",
348
+ "dependencies": {
349
+ "domelementtype": "^2.3.0"
350
+ },
351
+ "engines": {
352
+ "node": ">= 4"
353
+ },
354
+ "funding": {
355
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
356
+ }
357
+ },
358
+ "node_modules/domutils": {
359
+ "version": "3.2.2",
360
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
361
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
362
+ "license": "BSD-2-Clause",
363
+ "dependencies": {
364
+ "dom-serializer": "^2.0.0",
365
+ "domelementtype": "^2.3.0",
366
+ "domhandler": "^5.0.3"
367
+ },
368
+ "funding": {
369
+ "url": "https://github.com/fb55/domutils?sponsor=1"
370
+ }
371
+ },
372
+ "node_modules/dunder-proto": {
373
+ "version": "1.0.1",
374
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
375
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
376
+ "license": "MIT",
377
+ "dependencies": {
378
+ "call-bind-apply-helpers": "^1.0.1",
379
+ "es-errors": "^1.3.0",
380
+ "gopd": "^1.2.0"
381
+ },
382
+ "engines": {
383
+ "node": ">= 0.4"
384
+ }
385
+ },
386
+ "node_modules/ee-first": {
387
+ "version": "1.1.1",
388
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
389
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
390
+ "license": "MIT"
391
+ },
392
+ "node_modules/encodeurl": {
393
+ "version": "2.0.0",
394
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
395
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
396
+ "license": "MIT",
397
+ "engines": {
398
+ "node": ">= 0.8"
399
+ }
400
+ },
401
+ "node_modules/encoding-sniffer": {
402
+ "version": "0.2.1",
403
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
404
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
405
+ "license": "MIT",
406
+ "dependencies": {
407
+ "iconv-lite": "^0.6.3",
408
+ "whatwg-encoding": "^3.1.1"
409
+ },
410
+ "funding": {
411
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
412
+ }
413
+ },
414
+ "node_modules/encoding-sniffer/node_modules/iconv-lite": {
415
+ "version": "0.6.3",
416
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
417
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
418
+ "license": "MIT",
419
+ "dependencies": {
420
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
421
+ },
422
+ "engines": {
423
+ "node": ">=0.10.0"
424
+ }
425
+ },
426
+ "node_modules/entities": {
427
+ "version": "4.5.0",
428
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
429
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
430
+ "license": "BSD-2-Clause",
431
+ "engines": {
432
+ "node": ">=0.12"
433
+ },
434
+ "funding": {
435
+ "url": "https://github.com/fb55/entities?sponsor=1"
436
+ }
437
+ },
438
+ "node_modules/es-define-property": {
439
+ "version": "1.0.1",
440
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
441
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
442
+ "license": "MIT",
443
+ "engines": {
444
+ "node": ">= 0.4"
445
+ }
446
+ },
447
+ "node_modules/es-errors": {
448
+ "version": "1.3.0",
449
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
450
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
451
+ "license": "MIT",
452
+ "engines": {
453
+ "node": ">= 0.4"
454
+ }
455
+ },
456
+ "node_modules/es-object-atoms": {
457
+ "version": "1.1.1",
458
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
459
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
460
+ "license": "MIT",
461
+ "dependencies": {
462
+ "es-errors": "^1.3.0"
463
+ },
464
+ "engines": {
465
+ "node": ">= 0.4"
466
+ }
467
+ },
468
+ "node_modules/escape-html": {
469
+ "version": "1.0.3",
470
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
471
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
472
+ "license": "MIT"
473
+ },
474
+ "node_modules/etag": {
475
+ "version": "1.8.1",
476
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
477
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
478
+ "license": "MIT",
479
+ "engines": {
480
+ "node": ">= 0.6"
481
+ }
482
+ },
483
+ "node_modules/express": {
484
+ "version": "5.2.1",
485
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
486
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
487
+ "license": "MIT",
488
+ "dependencies": {
489
+ "accepts": "^2.0.0",
490
+ "body-parser": "^2.2.1",
491
+ "content-disposition": "^1.0.0",
492
+ "content-type": "^1.0.5",
493
+ "cookie": "^0.7.1",
494
+ "cookie-signature": "^1.2.1",
495
+ "debug": "^4.4.0",
496
+ "depd": "^2.0.0",
497
+ "encodeurl": "^2.0.0",
498
+ "escape-html": "^1.0.3",
499
+ "etag": "^1.8.1",
500
+ "finalhandler": "^2.1.0",
501
+ "fresh": "^2.0.0",
502
+ "http-errors": "^2.0.0",
503
+ "merge-descriptors": "^2.0.0",
504
+ "mime-types": "^3.0.0",
505
+ "on-finished": "^2.4.1",
506
+ "once": "^1.4.0",
507
+ "parseurl": "^1.3.3",
508
+ "proxy-addr": "^2.0.7",
509
+ "qs": "^6.14.0",
510
+ "range-parser": "^1.2.1",
511
+ "router": "^2.2.0",
512
+ "send": "^1.1.0",
513
+ "serve-static": "^2.2.0",
514
+ "statuses": "^2.0.1",
515
+ "type-is": "^2.0.1",
516
+ "vary": "^1.1.2"
517
+ },
518
+ "engines": {
519
+ "node": ">= 18"
520
+ },
521
+ "funding": {
522
+ "type": "opencollective",
523
+ "url": "https://opencollective.com/express"
524
+ }
525
+ },
526
+ "node_modules/finalhandler": {
527
+ "version": "2.1.1",
528
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
529
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
530
+ "license": "MIT",
531
+ "dependencies": {
532
+ "debug": "^4.4.0",
533
+ "encodeurl": "^2.0.0",
534
+ "escape-html": "^1.0.3",
535
+ "on-finished": "^2.4.1",
536
+ "parseurl": "^1.3.3",
537
+ "statuses": "^2.0.1"
538
+ },
539
+ "engines": {
540
+ "node": ">= 18.0.0"
541
+ },
542
+ "funding": {
543
+ "type": "opencollective",
544
+ "url": "https://opencollective.com/express"
545
+ }
546
+ },
547
+ "node_modules/forwarded": {
548
+ "version": "0.2.0",
549
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
550
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
551
+ "license": "MIT",
552
+ "engines": {
553
+ "node": ">= 0.6"
554
+ }
555
+ },
556
+ "node_modules/fresh": {
557
+ "version": "2.0.0",
558
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
559
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
560
+ "license": "MIT",
561
+ "engines": {
562
+ "node": ">= 0.8"
563
+ }
564
+ },
565
+ "node_modules/function-bind": {
566
+ "version": "1.1.2",
567
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
568
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
569
+ "license": "MIT",
570
+ "funding": {
571
+ "url": "https://github.com/sponsors/ljharb"
572
+ }
573
+ },
574
+ "node_modules/get-intrinsic": {
575
+ "version": "1.3.0",
576
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
577
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
578
+ "license": "MIT",
579
+ "dependencies": {
580
+ "call-bind-apply-helpers": "^1.0.2",
581
+ "es-define-property": "^1.0.1",
582
+ "es-errors": "^1.3.0",
583
+ "es-object-atoms": "^1.1.1",
584
+ "function-bind": "^1.1.2",
585
+ "get-proto": "^1.0.1",
586
+ "gopd": "^1.2.0",
587
+ "has-symbols": "^1.1.0",
588
+ "hasown": "^2.0.2",
589
+ "math-intrinsics": "^1.1.0"
590
+ },
591
+ "engines": {
592
+ "node": ">= 0.4"
593
+ },
594
+ "funding": {
595
+ "url": "https://github.com/sponsors/ljharb"
596
+ }
597
+ },
598
+ "node_modules/get-proto": {
599
+ "version": "1.0.1",
600
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
601
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
602
+ "license": "MIT",
603
+ "dependencies": {
604
+ "dunder-proto": "^1.0.1",
605
+ "es-object-atoms": "^1.0.0"
606
+ },
607
+ "engines": {
608
+ "node": ">= 0.4"
609
+ }
610
+ },
611
+ "node_modules/gopd": {
612
+ "version": "1.2.0",
613
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
614
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
615
+ "license": "MIT",
616
+ "engines": {
617
+ "node": ">= 0.4"
618
+ },
619
+ "funding": {
620
+ "url": "https://github.com/sponsors/ljharb"
621
+ }
622
+ },
623
+ "node_modules/has-symbols": {
624
+ "version": "1.1.0",
625
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
626
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
627
+ "license": "MIT",
628
+ "engines": {
629
+ "node": ">= 0.4"
630
+ },
631
+ "funding": {
632
+ "url": "https://github.com/sponsors/ljharb"
633
+ }
634
+ },
635
+ "node_modules/hasown": {
636
+ "version": "2.0.2",
637
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
638
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
639
+ "license": "MIT",
640
+ "dependencies": {
641
+ "function-bind": "^1.1.2"
642
+ },
643
+ "engines": {
644
+ "node": ">= 0.4"
645
+ }
646
+ },
647
+ "node_modules/htmlparser2": {
648
+ "version": "10.0.0",
649
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
650
+ "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
651
+ "funding": [
652
+ "https://github.com/fb55/htmlparser2?sponsor=1",
653
+ {
654
+ "type": "github",
655
+ "url": "https://github.com/sponsors/fb55"
656
+ }
657
+ ],
658
+ "license": "MIT",
659
+ "dependencies": {
660
+ "domelementtype": "^2.3.0",
661
+ "domhandler": "^5.0.3",
662
+ "domutils": "^3.2.1",
663
+ "entities": "^6.0.0"
664
+ }
665
+ },
666
+ "node_modules/htmlparser2/node_modules/entities": {
667
+ "version": "6.0.1",
668
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
669
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
670
+ "license": "BSD-2-Clause",
671
+ "engines": {
672
+ "node": ">=0.12"
673
+ },
674
+ "funding": {
675
+ "url": "https://github.com/fb55/entities?sponsor=1"
676
+ }
677
+ },
678
+ "node_modules/http-errors": {
679
+ "version": "2.0.1",
680
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
681
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
682
+ "license": "MIT",
683
+ "dependencies": {
684
+ "depd": "~2.0.0",
685
+ "inherits": "~2.0.4",
686
+ "setprototypeof": "~1.2.0",
687
+ "statuses": "~2.0.2",
688
+ "toidentifier": "~1.0.1"
689
+ },
690
+ "engines": {
691
+ "node": ">= 0.8"
692
+ },
693
+ "funding": {
694
+ "type": "opencollective",
695
+ "url": "https://opencollective.com/express"
696
+ }
697
+ },
698
+ "node_modules/iconv-lite": {
699
+ "version": "0.7.1",
700
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
701
+ "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
702
+ "license": "MIT",
703
+ "dependencies": {
704
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
705
+ },
706
+ "engines": {
707
+ "node": ">=0.10.0"
708
+ },
709
+ "funding": {
710
+ "type": "opencollective",
711
+ "url": "https://opencollective.com/express"
712
+ }
713
+ },
714
+ "node_modules/inherits": {
715
+ "version": "2.0.4",
716
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
717
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
718
+ "license": "ISC"
719
+ },
720
+ "node_modules/ipaddr.js": {
721
+ "version": "1.9.1",
722
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
723
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
724
+ "license": "MIT",
725
+ "engines": {
726
+ "node": ">= 0.10"
727
+ }
728
+ },
729
+ "node_modules/is-promise": {
730
+ "version": "4.0.0",
731
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
732
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
733
+ "license": "MIT"
734
+ },
735
+ "node_modules/kareem": {
736
+ "version": "3.0.0",
737
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz",
738
+ "integrity": "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA==",
739
+ "license": "Apache-2.0",
740
+ "engines": {
741
+ "node": ">=18.0.0"
742
+ }
743
+ },
744
+ "node_modules/math-intrinsics": {
745
+ "version": "1.1.0",
746
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
747
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
748
+ "license": "MIT",
749
+ "engines": {
750
+ "node": ">= 0.4"
751
+ }
752
+ },
753
+ "node_modules/media-typer": {
754
+ "version": "1.1.0",
755
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
756
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
757
+ "license": "MIT",
758
+ "engines": {
759
+ "node": ">= 0.8"
760
+ }
761
+ },
762
+ "node_modules/memory-pager": {
763
+ "version": "1.5.0",
764
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
765
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
766
+ "license": "MIT"
767
+ },
768
+ "node_modules/merge-descriptors": {
769
+ "version": "2.0.0",
770
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
771
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
772
+ "license": "MIT",
773
+ "engines": {
774
+ "node": ">=18"
775
+ },
776
+ "funding": {
777
+ "url": "https://github.com/sponsors/sindresorhus"
778
+ }
779
+ },
780
+ "node_modules/mime-db": {
781
+ "version": "1.54.0",
782
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
783
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
784
+ "license": "MIT",
785
+ "engines": {
786
+ "node": ">= 0.6"
787
+ }
788
+ },
789
+ "node_modules/mime-types": {
790
+ "version": "3.0.2",
791
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
792
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
793
+ "license": "MIT",
794
+ "dependencies": {
795
+ "mime-db": "^1.54.0"
796
+ },
797
+ "engines": {
798
+ "node": ">=18"
799
+ },
800
+ "funding": {
801
+ "type": "opencollective",
802
+ "url": "https://opencollective.com/express"
803
+ }
804
+ },
805
+ "node_modules/minimist": {
806
+ "version": "1.2.8",
807
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
808
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
809
+ "license": "MIT",
810
+ "funding": {
811
+ "url": "https://github.com/sponsors/ljharb"
812
+ }
813
+ },
814
+ "node_modules/mkdirp": {
815
+ "version": "0.5.6",
816
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
817
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
818
+ "license": "MIT",
819
+ "dependencies": {
820
+ "minimist": "^1.2.6"
821
+ },
822
+ "bin": {
823
+ "mkdirp": "bin/cmd.js"
824
+ }
825
+ },
826
+ "node_modules/mongodb": {
827
+ "version": "7.0.0",
828
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
829
+ "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
830
+ "license": "Apache-2.0",
831
+ "dependencies": {
832
+ "@mongodb-js/saslprep": "^1.3.0",
833
+ "bson": "^7.0.0",
834
+ "mongodb-connection-string-url": "^7.0.0"
835
+ },
836
+ "engines": {
837
+ "node": ">=20.19.0"
838
+ },
839
+ "peerDependencies": {
840
+ "@aws-sdk/credential-providers": "^3.806.0",
841
+ "@mongodb-js/zstd": "^7.0.0",
842
+ "gcp-metadata": "^7.0.1",
843
+ "kerberos": "^7.0.0",
844
+ "mongodb-client-encryption": ">=7.0.0 <7.1.0",
845
+ "snappy": "^7.3.2",
846
+ "socks": "^2.8.6"
847
+ },
848
+ "peerDependenciesMeta": {
849
+ "@aws-sdk/credential-providers": {
850
+ "optional": true
851
+ },
852
+ "@mongodb-js/zstd": {
853
+ "optional": true
854
+ },
855
+ "gcp-metadata": {
856
+ "optional": true
857
+ },
858
+ "kerberos": {
859
+ "optional": true
860
+ },
861
+ "mongodb-client-encryption": {
862
+ "optional": true
863
+ },
864
+ "snappy": {
865
+ "optional": true
866
+ },
867
+ "socks": {
868
+ "optional": true
869
+ }
870
+ }
871
+ },
872
+ "node_modules/mongodb-connection-string-url": {
873
+ "version": "7.0.0",
874
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz",
875
+ "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==",
876
+ "license": "Apache-2.0",
877
+ "dependencies": {
878
+ "@types/whatwg-url": "^13.0.0",
879
+ "whatwg-url": "^14.1.0"
880
+ },
881
+ "engines": {
882
+ "node": ">=20.19.0"
883
+ }
884
+ },
885
+ "node_modules/mongoose": {
886
+ "version": "9.1.2",
887
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.1.2.tgz",
888
+ "integrity": "sha512-EdxZ/l+wLIPymy/ODxulg13CaUPpt8RenGmuoNAuRhA5Dgk/QERkUm3U0wOEbAi6ULG08bGDo9sy2tKX0KwxIg==",
889
+ "license": "MIT",
890
+ "dependencies": {
891
+ "kareem": "3.0.0",
892
+ "mongodb": "~7.0",
893
+ "mpath": "0.9.0",
894
+ "mquery": "6.0.0",
895
+ "ms": "2.1.3",
896
+ "sift": "17.1.3"
897
+ },
898
+ "engines": {
899
+ "node": ">=20.19.0"
900
+ },
901
+ "funding": {
902
+ "type": "opencollective",
903
+ "url": "https://opencollective.com/mongoose"
904
+ }
905
+ },
906
+ "node_modules/mpath": {
907
+ "version": "0.9.0",
908
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
909
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
910
+ "license": "MIT",
911
+ "engines": {
912
+ "node": ">=4.0.0"
913
+ }
914
+ },
915
+ "node_modules/mquery": {
916
+ "version": "6.0.0",
917
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz",
918
+ "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==",
919
+ "license": "MIT",
920
+ "engines": {
921
+ "node": ">=20.19.0"
922
+ }
923
+ },
924
+ "node_modules/ms": {
925
+ "version": "2.1.3",
926
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
927
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
928
+ "license": "MIT"
929
+ },
930
+ "node_modules/multer": {
931
+ "version": "2.0.2",
932
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
933
+ "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
934
+ "license": "MIT",
935
+ "dependencies": {
936
+ "append-field": "^1.0.0",
937
+ "busboy": "^1.6.0",
938
+ "concat-stream": "^2.0.0",
939
+ "mkdirp": "^0.5.6",
940
+ "object-assign": "^4.1.1",
941
+ "type-is": "^1.6.18",
942
+ "xtend": "^4.0.2"
943
+ },
944
+ "engines": {
945
+ "node": ">= 10.16.0"
946
+ }
947
+ },
948
+ "node_modules/multer/node_modules/media-typer": {
949
+ "version": "0.3.0",
950
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
951
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
952
+ "license": "MIT",
953
+ "engines": {
954
+ "node": ">= 0.6"
955
+ }
956
+ },
957
+ "node_modules/multer/node_modules/mime-db": {
958
+ "version": "1.52.0",
959
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
960
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
961
+ "license": "MIT",
962
+ "engines": {
963
+ "node": ">= 0.6"
964
+ }
965
+ },
966
+ "node_modules/multer/node_modules/mime-types": {
967
+ "version": "2.1.35",
968
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
969
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
970
+ "license": "MIT",
971
+ "dependencies": {
972
+ "mime-db": "1.52.0"
973
+ },
974
+ "engines": {
975
+ "node": ">= 0.6"
976
+ }
977
+ },
978
+ "node_modules/multer/node_modules/type-is": {
979
+ "version": "1.6.18",
980
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
981
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
982
+ "license": "MIT",
983
+ "dependencies": {
984
+ "media-typer": "0.3.0",
985
+ "mime-types": "~2.1.24"
986
+ },
987
+ "engines": {
988
+ "node": ">= 0.6"
989
+ }
990
+ },
991
+ "node_modules/negotiator": {
992
+ "version": "1.0.0",
993
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
994
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
995
+ "license": "MIT",
996
+ "engines": {
997
+ "node": ">= 0.6"
998
+ }
999
+ },
1000
+ "node_modules/nth-check": {
1001
+ "version": "2.1.1",
1002
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
1003
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
1004
+ "license": "BSD-2-Clause",
1005
+ "dependencies": {
1006
+ "boolbase": "^1.0.0"
1007
+ },
1008
+ "funding": {
1009
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
1010
+ }
1011
+ },
1012
+ "node_modules/object-assign": {
1013
+ "version": "4.1.1",
1014
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1015
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1016
+ "license": "MIT",
1017
+ "engines": {
1018
+ "node": ">=0.10.0"
1019
+ }
1020
+ },
1021
+ "node_modules/object-inspect": {
1022
+ "version": "1.13.4",
1023
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1024
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1025
+ "license": "MIT",
1026
+ "engines": {
1027
+ "node": ">= 0.4"
1028
+ },
1029
+ "funding": {
1030
+ "url": "https://github.com/sponsors/ljharb"
1031
+ }
1032
+ },
1033
+ "node_modules/on-finished": {
1034
+ "version": "2.4.1",
1035
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1036
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1037
+ "license": "MIT",
1038
+ "dependencies": {
1039
+ "ee-first": "1.1.1"
1040
+ },
1041
+ "engines": {
1042
+ "node": ">= 0.8"
1043
+ }
1044
+ },
1045
+ "node_modules/once": {
1046
+ "version": "1.4.0",
1047
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1048
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1049
+ "license": "ISC",
1050
+ "dependencies": {
1051
+ "wrappy": "1"
1052
+ }
1053
+ },
1054
+ "node_modules/parse5": {
1055
+ "version": "7.3.0",
1056
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
1057
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
1058
+ "license": "MIT",
1059
+ "dependencies": {
1060
+ "entities": "^6.0.0"
1061
+ },
1062
+ "funding": {
1063
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
1064
+ }
1065
+ },
1066
+ "node_modules/parse5-htmlparser2-tree-adapter": {
1067
+ "version": "7.1.0",
1068
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
1069
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
1070
+ "license": "MIT",
1071
+ "dependencies": {
1072
+ "domhandler": "^5.0.3",
1073
+ "parse5": "^7.0.0"
1074
+ },
1075
+ "funding": {
1076
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
1077
+ }
1078
+ },
1079
+ "node_modules/parse5-parser-stream": {
1080
+ "version": "7.1.2",
1081
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
1082
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
1083
+ "license": "MIT",
1084
+ "dependencies": {
1085
+ "parse5": "^7.0.0"
1086
+ },
1087
+ "funding": {
1088
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
1089
+ }
1090
+ },
1091
+ "node_modules/parse5/node_modules/entities": {
1092
+ "version": "6.0.1",
1093
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
1094
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
1095
+ "license": "BSD-2-Clause",
1096
+ "engines": {
1097
+ "node": ">=0.12"
1098
+ },
1099
+ "funding": {
1100
+ "url": "https://github.com/fb55/entities?sponsor=1"
1101
+ }
1102
+ },
1103
+ "node_modules/parseurl": {
1104
+ "version": "1.3.3",
1105
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1106
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1107
+ "license": "MIT",
1108
+ "engines": {
1109
+ "node": ">= 0.8"
1110
+ }
1111
+ },
1112
+ "node_modules/path-to-regexp": {
1113
+ "version": "8.3.0",
1114
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
1115
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
1116
+ "license": "MIT",
1117
+ "funding": {
1118
+ "type": "opencollective",
1119
+ "url": "https://opencollective.com/express"
1120
+ }
1121
+ },
1122
+ "node_modules/proxy-addr": {
1123
+ "version": "2.0.7",
1124
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1125
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1126
+ "license": "MIT",
1127
+ "dependencies": {
1128
+ "forwarded": "0.2.0",
1129
+ "ipaddr.js": "1.9.1"
1130
+ },
1131
+ "engines": {
1132
+ "node": ">= 0.10"
1133
+ }
1134
+ },
1135
+ "node_modules/punycode": {
1136
+ "version": "2.3.1",
1137
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1138
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1139
+ "license": "MIT",
1140
+ "engines": {
1141
+ "node": ">=6"
1142
+ }
1143
+ },
1144
+ "node_modules/qs": {
1145
+ "version": "6.14.1",
1146
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
1147
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
1148
+ "license": "BSD-3-Clause",
1149
+ "dependencies": {
1150
+ "side-channel": "^1.1.0"
1151
+ },
1152
+ "engines": {
1153
+ "node": ">=0.6"
1154
+ },
1155
+ "funding": {
1156
+ "url": "https://github.com/sponsors/ljharb"
1157
+ }
1158
+ },
1159
+ "node_modules/range-parser": {
1160
+ "version": "1.2.1",
1161
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1162
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1163
+ "license": "MIT",
1164
+ "engines": {
1165
+ "node": ">= 0.6"
1166
+ }
1167
+ },
1168
+ "node_modules/raw-body": {
1169
+ "version": "3.0.2",
1170
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
1171
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
1172
+ "license": "MIT",
1173
+ "dependencies": {
1174
+ "bytes": "~3.1.2",
1175
+ "http-errors": "~2.0.1",
1176
+ "iconv-lite": "~0.7.0",
1177
+ "unpipe": "~1.0.0"
1178
+ },
1179
+ "engines": {
1180
+ "node": ">= 0.10"
1181
+ }
1182
+ },
1183
+ "node_modules/readable-stream": {
1184
+ "version": "3.6.2",
1185
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
1186
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
1187
+ "license": "MIT",
1188
+ "dependencies": {
1189
+ "inherits": "^2.0.3",
1190
+ "string_decoder": "^1.1.1",
1191
+ "util-deprecate": "^1.0.1"
1192
+ },
1193
+ "engines": {
1194
+ "node": ">= 6"
1195
+ }
1196
+ },
1197
+ "node_modules/router": {
1198
+ "version": "2.2.0",
1199
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
1200
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
1201
+ "license": "MIT",
1202
+ "dependencies": {
1203
+ "debug": "^4.4.0",
1204
+ "depd": "^2.0.0",
1205
+ "is-promise": "^4.0.0",
1206
+ "parseurl": "^1.3.3",
1207
+ "path-to-regexp": "^8.0.0"
1208
+ },
1209
+ "engines": {
1210
+ "node": ">= 18"
1211
+ }
1212
+ },
1213
+ "node_modules/safe-buffer": {
1214
+ "version": "5.2.1",
1215
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1216
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1217
+ "funding": [
1218
+ {
1219
+ "type": "github",
1220
+ "url": "https://github.com/sponsors/feross"
1221
+ },
1222
+ {
1223
+ "type": "patreon",
1224
+ "url": "https://www.patreon.com/feross"
1225
+ },
1226
+ {
1227
+ "type": "consulting",
1228
+ "url": "https://feross.org/support"
1229
+ }
1230
+ ],
1231
+ "license": "MIT"
1232
+ },
1233
+ "node_modules/safer-buffer": {
1234
+ "version": "2.1.2",
1235
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1236
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1237
+ "license": "MIT"
1238
+ },
1239
+ "node_modules/send": {
1240
+ "version": "1.2.1",
1241
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
1242
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
1243
+ "license": "MIT",
1244
+ "dependencies": {
1245
+ "debug": "^4.4.3",
1246
+ "encodeurl": "^2.0.0",
1247
+ "escape-html": "^1.0.3",
1248
+ "etag": "^1.8.1",
1249
+ "fresh": "^2.0.0",
1250
+ "http-errors": "^2.0.1",
1251
+ "mime-types": "^3.0.2",
1252
+ "ms": "^2.1.3",
1253
+ "on-finished": "^2.4.1",
1254
+ "range-parser": "^1.2.1",
1255
+ "statuses": "^2.0.2"
1256
+ },
1257
+ "engines": {
1258
+ "node": ">= 18"
1259
+ },
1260
+ "funding": {
1261
+ "type": "opencollective",
1262
+ "url": "https://opencollective.com/express"
1263
+ }
1264
+ },
1265
+ "node_modules/serve-static": {
1266
+ "version": "2.2.1",
1267
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
1268
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
1269
+ "license": "MIT",
1270
+ "dependencies": {
1271
+ "encodeurl": "^2.0.0",
1272
+ "escape-html": "^1.0.3",
1273
+ "parseurl": "^1.3.3",
1274
+ "send": "^1.2.0"
1275
+ },
1276
+ "engines": {
1277
+ "node": ">= 18"
1278
+ },
1279
+ "funding": {
1280
+ "type": "opencollective",
1281
+ "url": "https://opencollective.com/express"
1282
+ }
1283
+ },
1284
+ "node_modules/setprototypeof": {
1285
+ "version": "1.2.0",
1286
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1287
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1288
+ "license": "ISC"
1289
+ },
1290
+ "node_modules/side-channel": {
1291
+ "version": "1.1.0",
1292
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1293
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1294
+ "license": "MIT",
1295
+ "dependencies": {
1296
+ "es-errors": "^1.3.0",
1297
+ "object-inspect": "^1.13.3",
1298
+ "side-channel-list": "^1.0.0",
1299
+ "side-channel-map": "^1.0.1",
1300
+ "side-channel-weakmap": "^1.0.2"
1301
+ },
1302
+ "engines": {
1303
+ "node": ">= 0.4"
1304
+ },
1305
+ "funding": {
1306
+ "url": "https://github.com/sponsors/ljharb"
1307
+ }
1308
+ },
1309
+ "node_modules/side-channel-list": {
1310
+ "version": "1.0.0",
1311
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1312
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1313
+ "license": "MIT",
1314
+ "dependencies": {
1315
+ "es-errors": "^1.3.0",
1316
+ "object-inspect": "^1.13.3"
1317
+ },
1318
+ "engines": {
1319
+ "node": ">= 0.4"
1320
+ },
1321
+ "funding": {
1322
+ "url": "https://github.com/sponsors/ljharb"
1323
+ }
1324
+ },
1325
+ "node_modules/side-channel-map": {
1326
+ "version": "1.0.1",
1327
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1328
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "call-bound": "^1.0.2",
1332
+ "es-errors": "^1.3.0",
1333
+ "get-intrinsic": "^1.2.5",
1334
+ "object-inspect": "^1.13.3"
1335
+ },
1336
+ "engines": {
1337
+ "node": ">= 0.4"
1338
+ },
1339
+ "funding": {
1340
+ "url": "https://github.com/sponsors/ljharb"
1341
+ }
1342
+ },
1343
+ "node_modules/side-channel-weakmap": {
1344
+ "version": "1.0.2",
1345
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1346
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1347
+ "license": "MIT",
1348
+ "dependencies": {
1349
+ "call-bound": "^1.0.2",
1350
+ "es-errors": "^1.3.0",
1351
+ "get-intrinsic": "^1.2.5",
1352
+ "object-inspect": "^1.13.3",
1353
+ "side-channel-map": "^1.0.1"
1354
+ },
1355
+ "engines": {
1356
+ "node": ">= 0.4"
1357
+ },
1358
+ "funding": {
1359
+ "url": "https://github.com/sponsors/ljharb"
1360
+ }
1361
+ },
1362
+ "node_modules/sift": {
1363
+ "version": "17.1.3",
1364
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
1365
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
1366
+ "license": "MIT"
1367
+ },
1368
+ "node_modules/sparse-bitfield": {
1369
+ "version": "3.0.3",
1370
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1371
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1372
+ "license": "MIT",
1373
+ "dependencies": {
1374
+ "memory-pager": "^1.0.2"
1375
+ }
1376
+ },
1377
+ "node_modules/statuses": {
1378
+ "version": "2.0.2",
1379
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1380
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1381
+ "license": "MIT",
1382
+ "engines": {
1383
+ "node": ">= 0.8"
1384
+ }
1385
+ },
1386
+ "node_modules/streamsearch": {
1387
+ "version": "1.1.0",
1388
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1389
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1390
+ "engines": {
1391
+ "node": ">=10.0.0"
1392
+ }
1393
+ },
1394
+ "node_modules/string_decoder": {
1395
+ "version": "1.3.0",
1396
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1397
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1398
+ "license": "MIT",
1399
+ "dependencies": {
1400
+ "safe-buffer": "~5.2.0"
1401
+ }
1402
+ },
1403
+ "node_modules/toidentifier": {
1404
+ "version": "1.0.1",
1405
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1406
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1407
+ "license": "MIT",
1408
+ "engines": {
1409
+ "node": ">=0.6"
1410
+ }
1411
+ },
1412
+ "node_modules/tr46": {
1413
+ "version": "5.1.1",
1414
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
1415
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
1416
+ "license": "MIT",
1417
+ "dependencies": {
1418
+ "punycode": "^2.3.1"
1419
+ },
1420
+ "engines": {
1421
+ "node": ">=18"
1422
+ }
1423
+ },
1424
+ "node_modules/type-is": {
1425
+ "version": "2.0.1",
1426
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1427
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1428
+ "license": "MIT",
1429
+ "dependencies": {
1430
+ "content-type": "^1.0.5",
1431
+ "media-typer": "^1.1.0",
1432
+ "mime-types": "^3.0.0"
1433
+ },
1434
+ "engines": {
1435
+ "node": ">= 0.6"
1436
+ }
1437
+ },
1438
+ "node_modules/typedarray": {
1439
+ "version": "0.0.6",
1440
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1441
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
1442
+ "license": "MIT"
1443
+ },
1444
+ "node_modules/undici": {
1445
+ "version": "7.18.2",
1446
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz",
1447
+ "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==",
1448
+ "license": "MIT",
1449
+ "engines": {
1450
+ "node": ">=20.18.1"
1451
+ }
1452
+ },
1453
+ "node_modules/unpipe": {
1454
+ "version": "1.0.0",
1455
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1456
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1457
+ "license": "MIT",
1458
+ "engines": {
1459
+ "node": ">= 0.8"
1460
+ }
1461
+ },
1462
+ "node_modules/util-deprecate": {
1463
+ "version": "1.0.2",
1464
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1465
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1466
+ "license": "MIT"
1467
+ },
1468
+ "node_modules/vary": {
1469
+ "version": "1.1.2",
1470
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1471
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1472
+ "license": "MIT",
1473
+ "engines": {
1474
+ "node": ">= 0.8"
1475
+ }
1476
+ },
1477
+ "node_modules/webidl-conversions": {
1478
+ "version": "7.0.0",
1479
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1480
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1481
+ "license": "BSD-2-Clause",
1482
+ "engines": {
1483
+ "node": ">=12"
1484
+ }
1485
+ },
1486
+ "node_modules/whatwg-encoding": {
1487
+ "version": "3.1.1",
1488
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
1489
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
1490
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
1491
+ "license": "MIT",
1492
+ "dependencies": {
1493
+ "iconv-lite": "0.6.3"
1494
+ },
1495
+ "engines": {
1496
+ "node": ">=18"
1497
+ }
1498
+ },
1499
+ "node_modules/whatwg-encoding/node_modules/iconv-lite": {
1500
+ "version": "0.6.3",
1501
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1502
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1503
+ "license": "MIT",
1504
+ "dependencies": {
1505
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1506
+ },
1507
+ "engines": {
1508
+ "node": ">=0.10.0"
1509
+ }
1510
+ },
1511
+ "node_modules/whatwg-mimetype": {
1512
+ "version": "4.0.0",
1513
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
1514
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
1515
+ "license": "MIT",
1516
+ "engines": {
1517
+ "node": ">=18"
1518
+ }
1519
+ },
1520
+ "node_modules/whatwg-url": {
1521
+ "version": "14.2.0",
1522
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
1523
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
1524
+ "license": "MIT",
1525
+ "dependencies": {
1526
+ "tr46": "^5.1.0",
1527
+ "webidl-conversions": "^7.0.0"
1528
+ },
1529
+ "engines": {
1530
+ "node": ">=18"
1531
+ }
1532
+ },
1533
+ "node_modules/wrappy": {
1534
+ "version": "1.0.2",
1535
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1536
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1537
+ "license": "ISC"
1538
+ },
1539
+ "node_modules/xtend": {
1540
+ "version": "4.0.2",
1541
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1542
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
1543
+ "license": "MIT",
1544
+ "engines": {
1545
+ "node": ">=0.4"
1546
+ }
1547
+ }
1548
+ }
1549
+ }
package.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dependencies": {
3
+ "body-parser": "^2.2.2",
4
+ "cheerio": "^1.1.2",
5
+ "cors": "^2.8.5",
6
+ "express": "^5.2.1",
7
+ "mongoose": "^9.1.2",
8
+ "multer": "^2.0.2"
9
+ }
10
+ }
py.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from bs4 import BeautifulSoup
3
+ import re
4
+ from datetime import datetime
5
+
6
+ class MT5ReportParser:
7
+ def __init__(self, html_file_path):
8
+ """Initialize parser with HTML file path"""
9
+ self.html_file_path = html_file_path
10
+ self.soup = None
11
+ self.deals_df = None
12
+ self.statistics = {}
13
+
14
+ def load_html(self):
15
+ """Load and parse HTML file"""
16
+ with open(self.html_file_path, 'r', encoding='utf-8') as f:
17
+ content = f.read()
18
+ self.soup = BeautifulSoup(content, 'html.parser')
19
+
20
+ def extract_deals_table(self):
21
+ """Extract the Deals table from HTML"""
22
+ # Find all tables and look for the one with Deal data
23
+ tables = self.soup.find_all('table')
24
+
25
+ for table in tables:
26
+ headers = [th.get_text(strip=True) for th in table.find_all('th')]
27
+
28
+ # Check if this is the Deals table
29
+ if 'Deal' in headers or 'Time' in headers:
30
+ # Extract headers
31
+ cols = headers
32
+
33
+ # Extract rows
34
+ rows = []
35
+ for tr in table.find_all('tr')[1:]: # Skip header row
36
+ cells = tr.find_all('td')
37
+ if cells:
38
+ row = [td.get_text(strip=True) for td in cells]
39
+ rows.append(row)
40
+
41
+ # Create DataFrame
42
+ if rows:
43
+ self.deals_df = pd.DataFrame(rows, columns=cols)
44
+ self._clean_deals_data()
45
+ break
46
+
47
+ def _clean_deals_data(self):
48
+ """Clean and convert data types in deals DataFrame"""
49
+ if self.deals_df is None:
50
+ return
51
+
52
+ # Convert numeric columns
53
+ numeric_cols = ['Volume', 'Price', 'Commission', 'Swap', 'Profit', 'Balance']
54
+ for col in numeric_cols:
55
+ if col in self.deals_df.columns:
56
+ self.deals_df[col] = pd.to_numeric(
57
+ self.deals_df[col].str.replace(r'[^\d.-]', '', regex=True),
58
+ errors='coerce'
59
+ )
60
+
61
+ # Convert Time to datetime if possible
62
+ if 'Time' in self.deals_df.columns:
63
+ try:
64
+ self.deals_df['Time'] = pd.to_datetime(self.deals_df['Time'])
65
+ except:
66
+ pass
67
+
68
+ def extract_statistics(self):
69
+ """Extract all statistics from the report"""
70
+ # Find all table rows and extract key-value pairs
71
+ all_tables = self.soup.find_all('table')
72
+
73
+ for table in all_tables:
74
+ rows = table.find_all('tr')
75
+ for row in rows:
76
+ cells = row.find_all('td')
77
+ if len(cells) == 2:
78
+ key = cells[0].get_text(strip=True)
79
+ value = cells[1].get_text(strip=True)
80
+
81
+ # Clean up the key
82
+ key = key.replace(':', '').strip()
83
+
84
+ if key and value:
85
+ # Try to convert value to number
86
+ try:
87
+ value = float(value.replace(' ', '').replace(',', ''))
88
+ except:
89
+ pass
90
+
91
+ self.statistics[key] = value
92
+
93
+ # Also look for div-based statistics
94
+ divs = self.soup.find_all('div')
95
+ for div in divs:
96
+ text = div.get_text(strip=True)
97
+ if ':' in text:
98
+ parts = text.split(':')
99
+ if len(parts) == 2:
100
+ key = parts[0].strip()
101
+ value = parts[1].strip()
102
+ if key and value and key not in self.statistics:
103
+ try:
104
+ value = float(value.replace(' ', '').replace(',', ''))
105
+ except:
106
+ pass
107
+ self.statistics[key] = value
108
+
109
+ def get_common_statistics(self):
110
+ """Extract commonly used MT5 statistics"""
111
+ common_stats = {
112
+ 'Initial Deposit': None,
113
+ 'Total Net Profit': None,
114
+ 'Gross Profit': None,
115
+ 'Gross Loss': None,
116
+ 'Profit Factor': None,
117
+ 'Expected Payoff': None,
118
+ 'Absolute Drawdown': None,
119
+ 'Maximal Drawdown': None,
120
+ 'Relative Drawdown': None,
121
+ 'Total Trades': None,
122
+ 'Short Positions': None,
123
+ 'Long Positions': None,
124
+ 'Profit Trades': None,
125
+ 'Loss Trades': None,
126
+ 'Largest Profit': None,
127
+ 'Largest Loss': None,
128
+ 'Average Profit': None,
129
+ 'Average Loss': None,
130
+ 'Maximum Consecutive Wins': None,
131
+ 'Maximum Consecutive Losses': None,
132
+ 'Maximal Consecutive Profit': None,
133
+ 'Maximal Consecutive Loss': None,
134
+ 'Average Consecutive Wins': None,
135
+ 'Average Consecutive Losses': None,
136
+ }
137
+
138
+ # Try to match statistics with common names
139
+ for key in self.statistics:
140
+ for common_key in common_stats.keys():
141
+ if common_key.lower() in key.lower():
142
+ common_stats[common_key] = self.statistics[key]
143
+
144
+ return common_stats
145
+
146
+ def parse(self):
147
+ """Main method to parse the entire report"""
148
+ self.load_html()
149
+ self.extract_deals_table()
150
+ self.extract_statistics()
151
+
152
+ def save_deals_to_csv(self, output_path='deals.csv'):
153
+ """Save deals table to CSV"""
154
+ if self.deals_df is not None:
155
+ self.deals_df.to_csv(output_path, index=False)
156
+ print(f"Deals saved to {output_path}")
157
+ else:
158
+ print("No deals table found")
159
+
160
+ def save_statistics_to_csv(self, output_path='statistics.csv'):
161
+ """Save statistics to CSV"""
162
+ if self.statistics:
163
+ stats_df = pd.DataFrame(list(self.statistics.items()),
164
+ columns=['Statistic', 'Value'])
165
+ stats_df.to_csv(output_path, index=False)
166
+ print(f"Statistics saved to {output_path}")
167
+ else:
168
+ print("No statistics found")
169
+
170
+ def print_summary(self):
171
+ """Print a summary of the parsed data"""
172
+ print("=" * 60)
173
+ print("MT5 TRADE REPORT SUMMARY")
174
+ print("=" * 60)
175
+
176
+ if self.deals_df is not None:
177
+ print(f"\nDeals Table: {len(self.deals_df)} rows")
178
+ print(f"Columns: {', '.join(self.deals_df.columns.tolist())}")
179
+ print("\nFirst 5 deals:")
180
+ print(self.deals_df.head())
181
+ else:
182
+ print("\nNo deals table found")
183
+
184
+ print(f"\n\nStatistics Found: {len(self.statistics)}")
185
+ common_stats = self.get_common_statistics()
186
+ print("\nCommon Statistics:")
187
+ for key, value in common_stats.items():
188
+ if value is not None:
189
+ print(f" {key}: {value}")
190
+
191
+ print("\n" + "=" * 60)
192
+
193
+
194
+ # Example usage
195
+ if __name__ == "__main__":
196
+ # Replace with your HTML file path
197
+ html_file = "mt5_report.html"
198
+
199
+ # Create parser instance
200
+ parser = MT5ReportParser(html_file)
201
+
202
+ # Parse the report
203
+ parser.parse()
204
+
205
+ # Print summary
206
+ parser.print_summary()
207
+
208
+ # Save to CSV files
209
+ parser.save_deals_to_csv("deals.csv")
210
+ parser.save_statistics_to_csv("statistics.csv")
211
+
212
+ # Access the data programmatically
213
+ deals_df = parser.deals_df
214
+ statistics = parser.statistics
215
+ common_stats = parser.get_common_statistics()
216
+
217
+ # Example: Calculate total profit
218
+ if deals_df is not None and 'Profit' in deals_df.columns:
219
+ total_profit = deals_df['Profit'].sum()
220
+ print(f"\nTotal Profit from Deals: {total_profit}")
server/models/Report.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+
3
+ const DealSchema = new mongoose.Schema({
4
+ time: String,
5
+ deal: String,
6
+ symbol: String,
7
+ type: String,
8
+ direction: String,
9
+ volume: Number,
10
+ price: Number,
11
+ order: String,
12
+ commission: Number,
13
+ swap: Number,
14
+ profit: Number,
15
+ balance: Number,
16
+ comment: String,
17
+ });
18
+
19
+ const ReportSchema = new mongoose.Schema({
20
+ accountId: String,
21
+ uploadDate: { type: Date, default: Date.now },
22
+ deals: [DealSchema], // Stores the parsed table data
23
+ images: [String], // Stores paths/base64 of uploaded PNGs
24
+ });
25
+
26
+ module.exports = mongoose.model('Report', ReportSchema);
server/server.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const mongoose = require('mongoose');
3
+ const cors = require('cors');
4
+ const multer = require('multer');
5
+ const path = require('path');
6
+ const { parseMT5Report } = require('./utils/parser');
7
+ const Report = require('./models/Report');
8
+
9
+ const app = express();
10
+ app.use(cors());
11
+ app.use(express.json());
12
+ app.use('/uploads', express.static('uploads'));
13
+
14
+ // MongoDB Connection
15
+ mongoose.connect('mongodb://localhost:27017/mt5_analyzer');
16
+
17
+ // Multer Storage
18
+ const storage = multer.diskStorage({
19
+ destination: (req, file, cb) => cb(null, 'uploads/'),
20
+ filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname),
21
+ });
22
+ const upload = multer({ storage });
23
+
24
+ // API: Upload & Decode
25
+ app.post('/api/upload', upload.fields([{ name: 'htmlFile' }, { name: 'images' }]), async (req, res) => {
26
+ try {
27
+ const htmlFile = req.files['htmlFile'][0];
28
+ const imageFiles = req.files['images'] || [];
29
+
30
+ // Parse logic
31
+ const parsedDeals = parseMT5Report(htmlFile.path);
32
+
33
+ // Extract Account ID from filename (ReportTester-"_ID_".html)
34
+ const filename = htmlFile.originalname;
35
+ const accountId = filename.split('-')[1] || 'Unknown';
36
+
37
+ const newReport = new Report({
38
+ accountId,
39
+ deals: parsedDeals,
40
+ images: imageFiles.map(f => f.path)
41
+ });
42
+
43
+ await newReport.save();
44
+ res.json({ success: true, report: newReport });
45
+ } catch (err) {
46
+ console.error(err);
47
+ res.status(500).json({ error: 'Failed to process report' });
48
+ }
49
+ });
50
+
51
+ // API: Get All Reports
52
+ app.get('/api/reports', async (req, res) => {
53
+ const reports = await Report.find().sort({ uploadDate: -1 });
54
+ res.json(reports);
55
+ });
56
+
57
+ app.listen(5000, () => console.log('Server running on port 5000'));
server/utils/parser.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const cheerio = require('cheerio');
2
+ const fs = require('fs');
3
+
4
+ const parseMT5Report = (filePath) => {
5
+ const html = fs.readFileSync(filePath, 'utf8');
6
+ const $ = cheerio.load(html);
7
+ const deals = [];
8
+
9
+ // 1. Find the header "Deals" to locate the correct section
10
+ // The user specified: <th colspan="13" ...>Deals</th>
11
+ const dealsHeader = $('th:contains("Deals")');
12
+
13
+ if (dealsHeader.length > 0) {
14
+ // Find the parent table of this header
15
+ const table = dealsHeader.closest('table');
16
+
17
+ // Iterate through rows (skipping the header rows)
18
+ // We look for rows that contain numeric data or standard deal attributes
19
+ table.find('tr').each((i, row) => {
20
+ const cols = $(row).find('td');
21
+
22
+ // Basic validation to ensure it's a data row (13 columns based on prompt)
23
+ if (cols.length === 13) {
24
+ const rowData = {
25
+ time: $(cols[0]).text().trim(),
26
+ deal: $(cols[1]).text().trim(),
27
+ symbol: $(cols[2]).text().trim(),
28
+ type: $(cols[3]).text().trim(),
29
+ direction: $(cols[4]).text().trim(),
30
+ volume: parseFloat($(cols[5]).text().trim()),
31
+ price: parseFloat($(cols[6]).text().trim()),
32
+ order: $(cols[7]).text().trim(),
33
+ commission: parseFloat($(cols[8]).text().trim()),
34
+ swap: parseFloat($(cols[9]).text().trim()),
35
+ profit: parseFloat($(cols[10]).text().trim()),
36
+ balance: parseFloat($(cols[11]).text().trim()),
37
+ comment: $(cols[12]).text().trim(),
38
+ };
39
+
40
+ // Filter out header row repetitions if any exist by checking if 'balance' is a number
41
+ if (!isNaN(rowData.balance)) {
42
+ deals.push(rowData);
43
+ }
44
+ }
45
+ });
46
+ }
47
+ return deals;
48
+ };
49
+
50
+ module.exports = { parseMT5Report };