diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000000000000000000000000000000000000..5e0de70b02a944f34749d06dcaf860de237ae6f3 --- /dev/null +++ b/backend/.env @@ -0,0 +1,8 @@ +# 本地开发环境变量配置 +GITHUB_TOKEN=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re +GITHUB_REPOS=https://github.com/CaPaCaptain/PPTist_huggingface_db +JWT_SECRET=pptist-secret-key-2025-local +NODE_ENV=production +PORT=7860 +FRONTEND_URL=http://localhost:7860 +PUBLIC_URL=http://localhost:7860 \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..cd25b339b33598e8c833ebf2f65e9859e9648553 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,20 @@ +# Huggingface Space环境变量配置示例 +# 在Huggingface Space的Settings中设置这些环境变量 + +# GitHub配置 +GITHUB_TOKEN=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 +GITHUB_REPOS=https://github.com/CaPaCaptain/PPTist_huggingface_db + +# 如果有多个仓库,用逗号分隔: +# GITHUB_REPOS=https://github.com/CaPaCaptain/PPTist_huggingface_db,https://github.com/user/backup_repo + +# JWT密钥 +JWT_SECRET=pptist-secret-key-2025-huggingface + +# 服务配置 +NODE_ENV=production +PORT=7860 +FRONTEND_URL=* + +# 公共访问URL(可选,默认使用当前域名) +# PUBLIC_URL=your-space-name.hf.space \ No newline at end of file diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..a50594e90ffc5e0b93d82ee4b5e02edf70a065a8 --- /dev/null +++ b/backend/.eslintrc.js @@ -0,0 +1,33 @@ +export default { + env: { + node: true, + es2022: true, + jest: true + }, + extends: [ + 'eslint:recommended' + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + 'indent': ['error', 2], + 'linebreak-style': ['error', 'unix'], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], + 'no-console': 'warn', + 'no-debugger': 'error', + 'prefer-const': 'error', + 'no-var': 'error', + 'object-shorthand': 'error', + 'prefer-arrow-callback': 'error' + }, + globals: { + process: 'readonly', + Buffer: 'readonly', + __dirname: 'readonly', + __filename: 'readonly' + } +}; \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 0000000000000000000000000000000000000000..58cdde37de30e60add9b717f0bfa9754cf2f6e4a --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,11 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/backend/babel.config.js b/backend/babel.config.js new file mode 100644 index 0000000000000000000000000000000000000000..b1914d4228aa7b32944b6a943d2240713c0a574d --- /dev/null +++ b/backend/babel.config.js @@ -0,0 +1,12 @@ +export default { + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ] + ] +}; \ No newline at end of file diff --git a/backend/data/backup-state.json b/backend/data/backup-state.json new file mode 100644 index 0000000000000000000000000000000000000000..015dd760b6ec8eba88e972ae8501cec71650d464 --- /dev/null +++ b/backend/data/backup-state.json @@ -0,0 +1,19 @@ +{ + "lastBackupTime": "2025-07-04T08:00:00.390Z", + "backupStats": { + "totalBackups": 4, + "successfulBackups": 0, + "failedBackups": 3, + "lastBackupResult": { + "timestamp": "2025-07-04T08:00:00.390Z", + "success": false, + "totalUsers": 1, + "totalPPTs": 1, + "successfulBackups": 0, + "failedBackups": 1, + "errors": [], + "duration": 701 + } + }, + "savedAt": "2025-07-04T08:43:27.309Z" +} \ No newline at end of file diff --git a/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpeg b/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5a5e3bc56078640432c7c776de90fe37add84c33 Binary files /dev/null and b/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpeg differ diff --git a/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpg b/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/03a6cdc3c6a25a568cc4.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/0b23d8c33a69c858da20.jpg b/backend/data/persistent-links/0b23d8c33a69c858da20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/0b23d8c33a69c858da20.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/20cd33a270d601765d20.jpeg b/backend/data/persistent-links/20cd33a270d601765d20.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..bf9965d0d8cffe49637bfd7b6c9eaa4a90d92550 Binary files /dev/null and b/backend/data/persistent-links/20cd33a270d601765d20.jpeg differ diff --git a/backend/data/persistent-links/20cd33a270d601765d20.jpg b/backend/data/persistent-links/20cd33a270d601765d20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/20cd33a270d601765d20.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/28b9e6036c5c41e2e239.jpeg b/backend/data/persistent-links/28b9e6036c5c41e2e239.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d63339db10139fd7fc9808411601730be0a7c280 Binary files /dev/null and b/backend/data/persistent-links/28b9e6036c5c41e2e239.jpeg differ diff --git a/backend/data/persistent-links/28b9e6036c5c41e2e239.jpg b/backend/data/persistent-links/28b9e6036c5c41e2e239.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b7cc465b2eb042c675431ed78f1b72584fd15dd8 --- /dev/null +++ b/backend/data/persistent-links/28b9e6036c5c41e2e239.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 4 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/28dcce3fc7bd3c85c08f.jpg b/backend/data/persistent-links/28dcce3fc7bd3c85c08f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/28dcce3fc7bd3c85c08f.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/2ce3bf167eccf032652b.jpeg b/backend/data/persistent-links/2ce3bf167eccf032652b.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..bf9965d0d8cffe49637bfd7b6c9eaa4a90d92550 Binary files /dev/null and b/backend/data/persistent-links/2ce3bf167eccf032652b.jpeg differ diff --git a/backend/data/persistent-links/2ce3bf167eccf032652b.jpg b/backend/data/persistent-links/2ce3bf167eccf032652b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..938ff4b609f772ecc9a57daf41610c17ab28fe76 --- /dev/null +++ b/backend/data/persistent-links/2ce3bf167eccf032652b.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 4 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/3 19:14:00 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/30f2176fb7c999761b17.jpg b/backend/data/persistent-links/30f2176fb7c999761b17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/30f2176fb7c999761b17.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/3c8d15b9b029584a497c.jpg b/backend/data/persistent-links/3c8d15b9b029584a497c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/3c8d15b9b029584a497c.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/4bd5d90af592b7c7926a.jpeg b/backend/data/persistent-links/4bd5d90af592b7c7926a.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..54934fd048f4c902df930bdb13f9b801d51fdc71 Binary files /dev/null and b/backend/data/persistent-links/4bd5d90af592b7c7926a.jpeg differ diff --git a/backend/data/persistent-links/4bd5d90af592b7c7926a.jpg b/backend/data/persistent-links/4bd5d90af592b7c7926a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50fe48545aec08c3c033a0ae84d876c69bf27c24 --- /dev/null +++ b/backend/data/persistent-links/4bd5d90af592b7c7926a.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/79ee9ec855431826e944.jpg b/backend/data/persistent-links/79ee9ec855431826e944.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/79ee9ec855431826e944.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/7b8acd645ed91a600a9d.jpg b/backend/data/persistent-links/7b8acd645ed91a600a9d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/7b8acd645ed91a600a9d.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/7fc18e926bbae8a511b0.jpg b/backend/data/persistent-links/7fc18e926bbae8a511b0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/7fc18e926bbae8a511b0.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/871392505da1a438ec3e.jpeg b/backend/data/persistent-links/871392505da1a438ec3e.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f0d4b0d4b9d8a43e66c351c31ac49b9c5161f481 Binary files /dev/null and b/backend/data/persistent-links/871392505da1a438ec3e.jpeg differ diff --git a/backend/data/persistent-links/871392505da1a438ec3e.jpg b/backend/data/persistent-links/871392505da1a438ec3e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a6c1929152b0e878314aec13d5a830ba788ccec --- /dev/null +++ b/backend/data/persistent-links/871392505da1a438ec3e.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 3 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/8d026d2acda044060ba0.jpg b/backend/data/persistent-links/8d026d2acda044060ba0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/8d026d2acda044060ba0.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/93aacd822b7cb0333b44.jpg b/backend/data/persistent-links/93aacd822b7cb0333b44.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/93aacd822b7cb0333b44.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpeg b/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..85b24a564a78607b53059b44354240c454c83007 Binary files /dev/null and b/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpeg differ diff --git a/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpg b/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/9d9842c1fde7ad07ef99.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/bc11955adbe140b379d8.jpeg b/backend/data/persistent-links/bc11955adbe140b379d8.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0f2de3749df299a6b84bf6ff1a0b393a1c1fd22b Binary files /dev/null and b/backend/data/persistent-links/bc11955adbe140b379d8.jpeg differ diff --git a/backend/data/persistent-links/bc11955adbe140b379d8.png b/backend/data/persistent-links/bc11955adbe140b379d8.png new file mode 100644 index 0000000000000000000000000000000000000000..0f2de3749df299a6b84bf6ff1a0b393a1c1fd22b Binary files /dev/null and b/backend/data/persistent-links/bc11955adbe140b379d8.png differ diff --git a/backend/data/persistent-links/bc68b61c853557109f71.jpeg b/backend/data/persistent-links/bc68b61c853557109f71.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4651492c74ee4728ae478b7b0b5695cc072a2a4c Binary files /dev/null and b/backend/data/persistent-links/bc68b61c853557109f71.jpeg differ diff --git a/backend/data/persistent-links/bc68b61c853557109f71.jpg b/backend/data/persistent-links/bc68b61c853557109f71.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3509509a28b58eb18734a47d330495d6e8c3ce71 --- /dev/null +++ b/backend/data/persistent-links/bc68b61c853557109f71.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 1 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:01 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpeg b/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..74e76ff48f6d958688717fb6b1c26141f79980df Binary files /dev/null and b/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpeg differ diff --git a/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpg b/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/bd2e40bf50dcd09ce498.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpeg b/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ddc8ab84ae2aed6f13a0d59becc2efa368cd94c1 Binary files /dev/null and b/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpeg differ diff --git a/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpg b/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50fe48545aec08c3c033a0ae84d876c69bf27c24 --- /dev/null +++ b/backend/data/persistent-links/c4f2cb99960eb6ccd556.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/c5964b11efd468641097.jpeg b/backend/data/persistent-links/c5964b11efd468641097.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8157c4e99a78f3a0e2bfaaeb3fe2e99ddc3bd9f3 --- /dev/null +++ b/backend/data/persistent-links/c5964b11efd468641097.jpeg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 13:21:00 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/c5964b11efd468641097.jpg b/backend/data/persistent-links/c5964b11efd468641097.jpg new file mode 100644 index 0000000000000000000000000000000000000000..992150abcd72a9446ba8558064fe402b03653838 --- /dev/null +++ b/backend/data/persistent-links/c5964b11efd468641097.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 13:20:57 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/ce39926d0d37d02ba7da.jpeg b/backend/data/persistent-links/ce39926d0d37d02ba7da.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..eae84d97b6d7d682d8acb8d41ee15baa44ca2494 Binary files /dev/null and b/backend/data/persistent-links/ce39926d0d37d02ba7da.jpeg differ diff --git a/backend/data/persistent-links/ce39926d0d37d02ba7da.jpg b/backend/data/persistent-links/ce39926d0d37d02ba7da.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50fe48545aec08c3c033a0ae84d876c69bf27c24 --- /dev/null +++ b/backend/data/persistent-links/ce39926d0d37d02ba7da.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/e097929a40e192057c36.jpg b/backend/data/persistent-links/e097929a40e192057c36.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/e097929a40e192057c36.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/e564182b63f78d315a2b.jpg b/backend/data/persistent-links/e564182b63f78d315a2b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/e564182b63f78d315a2b.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/e57cc686dc7338ecad0f.jpeg b/backend/data/persistent-links/e57cc686dc7338ecad0f.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c2d37259c2799f18f6b69bdb898f78b626c215eb Binary files /dev/null and b/backend/data/persistent-links/e57cc686dc7338ecad0f.jpeg differ diff --git a/backend/data/persistent-links/e57cc686dc7338ecad0f.jpg b/backend/data/persistent-links/e57cc686dc7338ecad0f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b7cc465b2eb042c675431ed78f1b72584fd15dd8 --- /dev/null +++ b/backend/data/persistent-links/e57cc686dc7338ecad0f.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 4 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/ec49a9e55a145bce842b.jpeg b/backend/data/persistent-links/ec49a9e55a145bce842b.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..981051ea371410cfd425d999fb0747e70de79881 Binary files /dev/null and b/backend/data/persistent-links/ec49a9e55a145bce842b.jpeg differ diff --git a/backend/data/persistent-links/ec49a9e55a145bce842b.jpg b/backend/data/persistent-links/ec49a9e55a145bce842b.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/ec49a9e55a145bce842b.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/edeb6f6d7690ed843fa0.jpg b/backend/data/persistent-links/edeb6f6d7690ed843fa0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08b8b817adbed3e4bd9504ff9807ede3f91a3458 --- /dev/null +++ b/backend/data/persistent-links/edeb6f6d7690ed843fa0.jpg @@ -0,0 +1 @@ +Placeholder Image \ No newline at end of file diff --git a/backend/data/persistent-links/f352b2899707dcfd6795.jpeg b/backend/data/persistent-links/f352b2899707dcfd6795.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9e95b90e316c885205058e55d10c08b85c5ea3e0 Binary files /dev/null and b/backend/data/persistent-links/f352b2899707dcfd6795.jpeg differ diff --git a/backend/data/persistent-links/f352b2899707dcfd6795.jpg b/backend/data/persistent-links/f352b2899707dcfd6795.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a6c1929152b0e878314aec13d5a830ba788ccec --- /dev/null +++ b/backend/data/persistent-links/f352b2899707dcfd6795.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 3 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 19:01:33 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/f3940d573e09a2dab072.jpeg b/backend/data/persistent-links/f3940d573e09a2dab072.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ac6f66bd06eeb22f462234aece72493bec9193e2 --- /dev/null +++ b/backend/data/persistent-links/f3940d573e09a2dab072.jpeg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 13:28:54 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/f3940d573e09a2dab072.jpg b/backend/data/persistent-links/f3940d573e09a2dab072.jpg new file mode 100644 index 0000000000000000000000000000000000000000..17d6cb9eefd1ddb168c677c28843a3397a04ea70 --- /dev/null +++ b/backend/data/persistent-links/f3940d573e09a2dab072.jpg @@ -0,0 +1,21 @@ + + + + + + + + + + 第 未知 页 + + 包含 2 个元素 + + PPT ID: 9848393a-f385-4889-9967-5c708e08fc70 | 页面: 未知 + + 生成时间: 2025/7/4 13:28:48 + + + + 未知 + \ No newline at end of file diff --git a/backend/data/persistent-links/link-map.json b/backend/data/persistent-links/link-map.json new file mode 100644 index 0000000000000000000000000000000000000000..5db3fea01c84ef7c4c263eebb33bde10efbeb41d --- /dev/null +++ b/backend/data/persistent-links/link-map.json @@ -0,0 +1,541 @@ +{ + "version": "1.0", + "lastUpdated": "2025-07-04T11:01:39.959Z", + "persistentLinks": { + "6b1c1e179d18f48d71d8": { + "linkId": "6b1c1e179d18f48d71d8", + "userId": "test-user-1751275334103", + "pptId": "test-ppt-1751275334103", + "pageIndex": 0, + "createdAt": "2025-06-30T09:22:19.109Z", + "lastUpdated": "2025-06-30T09:22:19.109Z", + "updateCount": 0, + "hasImage": false + }, + "bc11955adbe140b379d8": { + "linkId": "bc11955adbe140b379d8", + "userId": "PS02", + "pptId": "77330288-0b80-4c3b-987d-440b20282bc5", + "pageIndex": 0, + "createdAt": "2025-07-01T06:41:59.958Z", + "lastUpdated": "2025-07-02T11:37:51.637Z", + "updateCount": 2, + "hasImage": true, + "imageSize": 70, + "format": "png", + "imagePath": "data\\persistent-links\\bc11955adbe140b379d8.png", + "metadata": { + "testMode": true, + "timestamp": "2025-07-02T11:37:51.629Z", + "exportedAt": "2025-07-02T11:37:51.636Z", + "slideTitle": "第 1 页", + "exportMethod": "frontend" + } + }, + "bd2e40bf50dcd09ce498": { + "linkId": "bd2e40bf50dcd09ce498", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": null, + "pageIndex": 1, + "uniqueId": 1, + "createdAt": "2025-07-02T12:05:08.565Z", + "lastUpdated": "2025-07-03T10:31:22.765Z", + "updateCount": 19, + "hasImage": true, + "imageSize": 50236, + "format": "jpeg", + "imagePath": "data\\persistent-links\\bd2e40bf50dcd09ce498.jpeg", + "metadata": { + "slideId": "mGzShRon0M", + "slideTitle": "第 2 页", + "quality": 0.9, + "shareUrl": "http://localhost:5173/api/public/image/PS01/9848393a-f385-4889-9967-5c708e08fc70/1?slideId=mGzShRon0M&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "originalShareUrl": "http://localhost:5173/api/public/image/PS01/9848393a-f385-4889-9967-5c708e08fc70/1", + "exportedAt": "2025-07-03T10:31:22.765Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "ec49a9e55a145bce842b": { + "linkId": "ec49a9e55a145bce842b", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": null, + "pageIndex": 2, + "uniqueId": 2, + "createdAt": "2025-07-02T12:09:23.977Z", + "lastUpdated": "2025-07-03T10:37:10.115Z", + "updateCount": 31, + "hasImage": true, + "imageSize": 7645, + "format": "jpeg", + "imagePath": "data\\persistent-links\\ec49a9e55a145bce842b.jpeg", + "metadata": { + "slideId": "mGzShRon0M", + "slideTitle": "第 3 页", + "quality": 0.9, + "shareUrl": "http://localhost:7860/api/persistent-images/9848393a-f385-4889-9967-5c708e08fc70/mGzShRon0M?slideId=mGzShRon0M&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "exportedAt": "2025-07-03T10:37:10.115Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "20cd33a270d601765d20": { + "linkId": "20cd33a270d601765d20", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": null, + "pageIndex": 3, + "uniqueId": 3, + "createdAt": "2025-07-02T12:14:17.636Z", + "lastUpdated": "2025-07-03T10:50:39.914Z", + "updateCount": 15, + "hasImage": true, + "imageSize": 9007, + "format": "jpeg", + "imagePath": "data\\persistent-links\\20cd33a270d601765d20.jpeg", + "metadata": { + "slideId": "mGzShRon0M", + "slideTitle": "第 4 页", + "quality": 0.9, + "shareUrl": "http://localhost:7860/api/persistent-images/9848393a-f385-4889-9967-5c708e08fc70?slideId=mGzShRon0M&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "exportedAt": "2025-07-03T10:50:39.914Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "03a6cdc3c6a25a568cc4": { + "linkId": "03a6cdc3c6a25a568cc4", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": null, + "pageIndex": null, + "uniqueId": 0, + "createdAt": "2025-07-02T12:57:23.783Z", + "lastUpdated": "2025-07-03T10:31:38.764Z", + "updateCount": 8, + "hasImage": true, + "imageSize": 6365, + "format": "jpeg", + "imagePath": "data\\persistent-links\\03a6cdc3c6a25a568cc4.jpeg", + "metadata": { + "slideId": "mK0RdDqJqw", + "slideTitle": "第 1 页", + "quality": 0.9, + "shareUrl": "http://localhost:7860/api/persistent-images/9848393a-f385-4889-9967-5c708e08fc70/mK0RdDqJqw?slideId=mK0RdDqJqw&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "exportedAt": "2025-07-03T10:31:38.763Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "9d9842c1fde7ad07ef99": { + "linkId": "9d9842c1fde7ad07ef99", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": null, + "pageIndex": 4, + "uniqueId": 4, + "createdAt": "2025-07-03T03:04:10.595Z", + "lastUpdated": "2025-07-03T10:50:58.602Z", + "updateCount": 16, + "hasImage": true, + "imageSize": 8865, + "format": "jpeg", + "imagePath": "data\\persistent-links\\9d9842c1fde7ad07ef99.jpeg", + "metadata": { + "slideId": "arO5qMy33M", + "slideTitle": "第 5 页", + "quality": 0.9, + "shareUrl": "http://localhost:7860/api/persistent-images/9848393a-f385-4889-9967-5c708e08fc70?slideId=arO5qMy33M&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "exportedAt": "2025-07-03T10:50:58.601Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "f352b2899707dcfd6795": { + "linkId": "f352b2899707dcfd6795", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "mK0RdDqJqw", + "pageIndex": null, + "uniqueId": "mK0RdDqJqw", + "createdAt": "2025-07-04T11:00:28.136Z", + "lastUpdated": "2025-07-04T11:01:34.137Z", + "updateCount": 6, + "hasImage": true, + "imageSize": 23579, + "format": "jpeg", + "imagePath": "data\\persistent-links\\f352b2899707dcfd6795.jpeg", + "metadata": { + "exportedAt": "2025-07-04T11:01:19.240Z", + "slideTitle": "第 1 页", + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "c4f2cb99960eb6ccd556": { + "linkId": "c4f2cb99960eb6ccd556", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "arO5qMy33M", + "pageIndex": 4, + "uniqueId": "arO5qMy33M", + "createdAt": "2025-07-04T11:01:01.772Z", + "lastUpdated": "2025-07-04T11:01:39.959Z", + "updateCount": 5, + "hasImage": true, + "imageSize": 5069, + "format": "jpeg", + "imagePath": "data\\persistent-links\\c4f2cb99960eb6ccd556.jpeg", + "metadata": { + "exportedAt": "2025-07-04T11:01:39.958Z", + "slideTitle": "第 5 页", + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "2ce3bf167eccf032652b": { + "linkId": "2ce3bf167eccf032652b", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "mGzShRon0M", + "pageIndex": 3, + "uniqueId": "mGzShRon0M", + "createdAt": "2025-07-03T11:00:23.530Z", + "lastUpdated": "2025-07-03T11:14:00.613Z", + "updateCount": 9, + "hasImage": true, + "imageSize": 1528, + "format": "jpg", + "imagePath": "data\\persistent-links\\2ce3bf167eccf032652b.jpg", + "metadata": { + "slideId": "mGzShRon0M", + "slideTitle": "第 4 页", + "quality": 0.9, + "shareUrl": "http://localhost:7860/api/persistent-images/2ce3bf167eccf032652b?slideId=mGzShRon0M&pptId=9848393a-f385-4889-9967-5c708e08fc70", + "exportedAt": "2025-07-03T11:13:11.149Z", + "viewportRatio": 0.5625, + "exportMethod": "frontend", + "isCurrentSlide": true + } + }, + "ce39926d0d37d02ba7da": { + "linkId": "ce39926d0d37d02ba7da", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "qV-X4UilCE", + "pageIndex": null, + "uniqueId": "qV-X4UilCE", + "createdAt": "2025-07-04T11:01:01.769Z", + "lastUpdated": "2025-07-04T11:01:34.874Z", + "updateCount": 4, + "hasImage": true, + "imageSize": 26627, + "format": "jpeg", + "imagePath": "data\\persistent-links\\ce39926d0d37d02ba7da.jpeg" + }, + "28b9e6036c5c41e2e239": { + "linkId": "28b9e6036c5c41e2e239", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "pageIndex": null, + "uniqueId": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "createdAt": "2025-07-04T11:01:01.765Z", + "lastUpdated": "2025-07-04T11:01:34.399Z", + "updateCount": 4, + "hasImage": true, + "imageSize": 21694, + "format": "jpeg", + "imagePath": "data\\persistent-links\\28b9e6036c5c41e2e239.jpeg" + }, + "e17c54d3eb97b911cfc8": { + "linkId": "e17c54d3eb97b911cfc8", + "userId": "PS01", + "pptId": "test-ppt-1751513483395", + "slideId": "slide-abc123", + "pageIndex": null, + "uniqueId": "slide-abc123", + "createdAt": "2025-07-03T03:31:23.425Z", + "lastUpdated": "2025-07-03T03:31:23.425Z", + "updateCount": 0, + "hasImage": false + }, + "8d026d2acda044060ba0": { + "linkId": "8d026d2acda044060ba0", + "userId": "PS01", + "pptId": "test-ppt-1751513779599", + "slideId": "slide-abc123", + "pageIndex": null, + "uniqueId": "slide-abc123", + "createdAt": "2025-07-03T03:36:19.630Z", + "lastUpdated": "2025-07-03T03:36:19.637Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\8d026d2acda044060ba0.jpg" + }, + "0b23d8c33a69c858da20": { + "linkId": "0b23d8c33a69c858da20", + "userId": "PS01", + "pptId": "test-ppt-1751513779599", + "slideId": "slide-def456", + "pageIndex": null, + "uniqueId": "slide-def456", + "createdAt": "2025-07-03T03:36:19.647Z", + "lastUpdated": "2025-07-03T03:36:19.668Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\0b23d8c33a69c858da20.jpg" + }, + "93aacd822b7cb0333b44": { + "linkId": "93aacd822b7cb0333b44", + "userId": "PS01", + "pptId": "test-ppt-1751513779599", + "slideId": "slide-ghi789", + "pageIndex": null, + "uniqueId": "slide-ghi789", + "createdAt": "2025-07-03T03:36:19.673Z", + "lastUpdated": "2025-07-03T03:36:19.677Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\93aacd822b7cb0333b44.jpg" + }, + "7fc18e926bbae8a511b0": { + "linkId": "7fc18e926bbae8a511b0", + "userId": "PS01", + "pptId": "test-ppt-1751513779599-batch", + "slideId": "slide-abc123", + "pageIndex": null, + "uniqueId": "slide-abc123", + "createdAt": "2025-07-03T03:36:19.681Z", + "lastUpdated": "2025-07-03T03:36:19.685Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\7fc18e926bbae8a511b0.jpg" + }, + "3c8d15b9b029584a497c": { + "linkId": "3c8d15b9b029584a497c", + "userId": "PS01", + "pptId": "test-ppt-1751513779599-batch", + "slideId": "slide-def456", + "pageIndex": null, + "uniqueId": "slide-def456", + "createdAt": "2025-07-03T03:36:19.686Z", + "lastUpdated": "2025-07-03T03:36:19.689Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\3c8d15b9b029584a497c.jpg" + }, + "edeb6f6d7690ed843fa0": { + "linkId": "edeb6f6d7690ed843fa0", + "userId": "PS01", + "pptId": "test-ppt-1751513779599-batch", + "slideId": "slide-ghi789", + "pageIndex": null, + "uniqueId": "slide-ghi789", + "createdAt": "2025-07-03T03:36:19.691Z", + "lastUpdated": "2025-07-03T03:36:19.694Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\edeb6f6d7690ed843fa0.jpg" + }, + "e097929a40e192057c36": { + "linkId": "e097929a40e192057c36", + "userId": "PS01", + "pptId": "test-ppt-1751513848900", + "slideId": "slide-abc123", + "pageIndex": null, + "uniqueId": "slide-abc123", + "createdAt": "2025-07-03T03:37:28.929Z", + "lastUpdated": "2025-07-03T03:37:28.937Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\e097929a40e192057c36.jpg" + }, + "e564182b63f78d315a2b": { + "linkId": "e564182b63f78d315a2b", + "userId": "PS01", + "pptId": "test-ppt-1751513848900", + "slideId": "slide-def456", + "pageIndex": null, + "uniqueId": "slide-def456", + "createdAt": "2025-07-03T03:37:28.946Z", + "lastUpdated": "2025-07-03T03:37:28.950Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\e564182b63f78d315a2b.jpg" + }, + "28dcce3fc7bd3c85c08f": { + "linkId": "28dcce3fc7bd3c85c08f", + "userId": "PS01", + "pptId": "test-ppt-1751513848900", + "slideId": "slide-ghi789", + "pageIndex": null, + "uniqueId": "slide-ghi789", + "createdAt": "2025-07-03T03:37:28.954Z", + "lastUpdated": "2025-07-03T03:37:28.957Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\28dcce3fc7bd3c85c08f.jpg" + }, + "7b8acd645ed91a600a9d": { + "linkId": "7b8acd645ed91a600a9d", + "userId": "PS01", + "pptId": "test-ppt-1751513848900-batch", + "slideId": "slide-abc123", + "pageIndex": null, + "uniqueId": "slide-abc123", + "createdAt": "2025-07-03T03:37:28.962Z", + "lastUpdated": "2025-07-03T03:37:28.990Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\7b8acd645ed91a600a9d.jpg" + }, + "30f2176fb7c999761b17": { + "linkId": "30f2176fb7c999761b17", + "userId": "PS01", + "pptId": "test-ppt-1751513848900-batch", + "slideId": "slide-def456", + "pageIndex": null, + "uniqueId": "slide-def456", + "createdAt": "2025-07-03T03:37:28.992Z", + "lastUpdated": "2025-07-03T03:37:28.995Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\30f2176fb7c999761b17.jpg" + }, + "79ee9ec855431826e944": { + "linkId": "79ee9ec855431826e944", + "userId": "PS01", + "pptId": "test-ppt-1751513848900-batch", + "slideId": "slide-ghi789", + "pageIndex": null, + "uniqueId": "slide-ghi789", + "createdAt": "2025-07-03T03:37:28.996Z", + "lastUpdated": "2025-07-03T03:37:28.998Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 17, + "format": "jpg", + "imagePath": "data\\persistent-links\\79ee9ec855431826e944.jpg" + }, + "e57cc686dc7338ecad0f": { + "linkId": "e57cc686dc7338ecad0f", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "tGq5X9TW40", + "pageIndex": null, + "uniqueId": "tGq5X9TW40", + "createdAt": "2025-07-04T11:01:01.794Z", + "lastUpdated": "2025-07-04T11:01:35.278Z", + "updateCount": 4, + "hasImage": true, + "imageSize": 31538, + "format": "jpeg", + "imagePath": "data\\persistent-links\\e57cc686dc7338ecad0f.jpeg" + }, + "bc68b61c853557109f71": { + "linkId": "bc68b61c853557109f71", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "aYABLP3iWe", + "pageIndex": null, + "uniqueId": "aYABLP3iWe", + "createdAt": "2025-07-04T11:01:01.771Z", + "lastUpdated": "2025-07-04T11:01:08.334Z", + "updateCount": 2, + "hasImage": true, + "imageSize": 6255, + "format": "jpeg", + "imagePath": "data\\persistent-links\\bc68b61c853557109f71.jpeg" + }, + "871392505da1a438ec3e": { + "linkId": "871392505da1a438ec3e", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "OqhnuHq1d1", + "pageIndex": null, + "uniqueId": "OqhnuHq1d1", + "createdAt": "2025-07-04T11:01:01.767Z", + "lastUpdated": "2025-07-04T11:01:34.643Z", + "updateCount": 4, + "hasImage": true, + "imageSize": 27139, + "format": "jpeg", + "imagePath": "data\\persistent-links\\871392505da1a438ec3e.jpeg" + }, + "c5964b11efd468641097": { + "linkId": "c5964b11efd468641097", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "Jlj-F7B0oc", + "pageIndex": null, + "uniqueId": "Jlj-F7B0oc", + "createdAt": "2025-07-04T05:21:00.090Z", + "lastUpdated": "2025-07-04T05:21:00.199Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 1525, + "format": "jpeg", + "imagePath": "data\\persistent-links\\c5964b11efd468641097.jpeg" + }, + "f3940d573e09a2dab072": { + "linkId": "f3940d573e09a2dab072", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "Zh1YNwJB6P", + "pageIndex": null, + "uniqueId": "Zh1YNwJB6P", + "createdAt": "2025-07-04T05:28:54.307Z", + "lastUpdated": "2025-07-04T05:28:54.376Z", + "updateCount": 1, + "hasImage": true, + "imageSize": 1525, + "format": "jpeg", + "imagePath": "data\\persistent-links\\f3940d573e09a2dab072.jpeg" + }, + "4bd5d90af592b7c7926a": { + "linkId": "4bd5d90af592b7c7926a", + "userId": "PS01", + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "slideId": "4Stthdu2Bf", + "pageIndex": null, + "uniqueId": "4Stthdu2Bf", + "createdAt": "2025-07-04T11:01:01.809Z", + "lastUpdated": "2025-07-04T11:01:35.755Z", + "updateCount": 4, + "hasImage": true, + "imageSize": 15641, + "format": "jpeg", + "imagePath": "data\\persistent-links\\4bd5d90af592b7c7926a.jpeg" + } + } +} \ No newline at end of file diff --git a/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/data.json b/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/data.json new file mode 100644 index 0000000000000000000000000000000000000000..8a0b38647bd03c220824de10577b29374de2531f --- /dev/null +++ b/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/data.json @@ -0,0 +1,2893 @@ +{ + "id": "9848393a-f385-4889-9967-5c708e08fc70", + "title": "未命名演示文稿", + "slides": [ + { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 5.035971223021647, + "top": -30.695443645083934, + "width": 628.2973621103117, + "height": 574.5803357314148, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 574.9000799360512, + "top": 50.59063860023091, + "width": 402.8776978417266, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "chart", + "id": "5ceF1OBvLU", + "chartType": "radar", + "left": 644.4444444444445, + "top": 281.25, + "width": 276.5432098765432, + "height": 245.679012345679, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 5.035971223021647, + "top": -30.695443645083934, + "width": 628.2973621103117, + "height": 574.5803357314148, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 560.431654676259, + "top": 92.56594724220622, + "width": 402.8776978417266, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 5.035971223021647, + "top": -30.695443645083934, + "width": 628.2973621103117, + "height": 574.5803357314148, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 560.431654676259, + "top": 92.56594724220622, + "width": 402.8776978417266, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 5.035971223021647, + "top": -30.695443645083934, + "width": 628.2973621103117, + "height": 574.5803357314148, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 560.431654676259, + "top": 92.56594724220622, + "width": 402.8776978417266, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 148.4412470023981, + "top": 0, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 487.5299760191847, + "top": 23.501199040767382, + "width": 145.80335731414868, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 148.4412470023981, + "top": 0, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 487.5299760191847, + "top": 23.501199040767382, + "width": 145.80335731414868, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 148.4412470023981, + "top": 0, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "zE9_Ulzm6r", + "left": 487.5299760191847, + "top": 23.501199040767382, + "width": 145.80335731414868, + "height": 176.49880095923263, + "viewBox": [ + 200, + 200 + ], + "path": "M 70 20 L 0 160 Q 0 200 40 200 L 160 200 Q 200 200 200 160 L 130 20 Q 100 -20 70 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 386.33093525179856, + "top": 44.79916067146283, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 386.33093525179856, + "top": 44.79916067146283, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 0, + "props": { + "id": "mK0RdDqJqw", + "elements": [ + { + "type": "chart", + "id": "71IUf5sdzJ", + "chartType": "ring", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:41:47.915Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:41:55.720Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:42:04.400Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:46:07.118Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:46:29.014Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:47:34.140Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:49:04.384Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:49:08.309Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:49:25.168Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:55:45.064Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:56:18.551Z" + }, + "imageLink": "/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-03T12:56:59.628Z" + }, + "imageLink": "http://localhost:7860/api/persistent-images/f352b2899707dcfd6795", + "linkId": "f352b2899707dcfd6795", + "lastImageUpdate": "2025-07-04T03:14:20.575Z", + "lastModified": "2025-07-04T11:01:00.639Z" + }, + { + "id": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "elements": [ + { + "type": "text", + "id": "70a2e8a6-a557-4eed-b041-2ba0161a7688", + "left": 150, + "top": 200, + "width": 600, + "height": 44, + "content": "未命名演示文稿", + "fontSize": 32, + "fontName": "Microsoft YaHei", + "defaultColor": "#333333", + "bold": true, + "align": "center" + }, + { + "type": "chart", + "id": "k3Y-GmFJEx", + "chartType": "bar", + "left": 463.30891330891336, + "top": 87.50726289443104, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "UDYhoUqpzR", + "left": 434.47187031257835, + "top": 190.68961913209702, + "width": 173.3821733821734, + "height": 152.62515262515262, + "viewBox": [ + 173.3821733821734, + 152.62515262515262 + ], + "path": "M 0 0 L 130.03663003663004 0 L 173.3821733821734 152.62515262515262 L 43.34554334554335 152.62515262515262 Z", + "fill": "#ff1e02", + "fixedRatio": false, + "rotate": 0, + "pathFormula": "parallelogramRight", + "keypoints": [ + 0.25 + ] + }, + { + "type": "chart", + "id": "l1t3_TZWua", + "chartType": "pie", + "left": 59.56636709173655, + "top": 36.85468698058763, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 1, + "props": { + "id": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "elements": [ + { + "type": "text", + "id": "70a2e8a6-a557-4eed-b041-2ba0161a7688", + "left": 150, + "top": 200, + "width": 600, + "height": 44, + "content": "未命名演示文稿", + "fontSize": 32, + "fontName": "Microsoft YaHei", + "defaultColor": "#333333", + "bold": true, + "align": "center" + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 1, + "props": { + "id": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "elements": [ + { + "type": "text", + "id": "70a2e8a6-a557-4eed-b041-2ba0161a7688", + "left": 150, + "top": 200, + "width": 600, + "height": 44, + "content": "未命名演示文稿", + "fontSize": 32, + "fontName": "Microsoft YaHei", + "defaultColor": "#333333", + "bold": true, + "align": "center" + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 1, + "props": { + "id": "f2ba3db2-2124-41e1-817b-5f919cee4ade", + "elements": [ + { + "type": "text", + "id": "70a2e8a6-a557-4eed-b041-2ba0161a7688", + "left": 150, + "top": 200, + "width": 600, + "height": 44, + "content": "未命名演示文稿", + "fontSize": 32, + "fontName": "Microsoft YaHei", + "defaultColor": "#333333", + "bold": true, + "align": "center" + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "imageLink": "/api/persistent-images/28b9e6036c5c41e2e239", + "linkId": "28b9e6036c5c41e2e239", + "lastImageUpdate": "2025-07-03T12:55:45.312Z" + }, + "imageLink": "/api/persistent-images/28b9e6036c5c41e2e239", + "linkId": "28b9e6036c5c41e2e239", + "lastImageUpdate": "2025-07-03T12:56:18.801Z" + }, + "imageLink": "/api/persistent-images/28b9e6036c5c41e2e239", + "linkId": "28b9e6036c5c41e2e239", + "lastImageUpdate": "2025-07-03T12:56:59.882Z" + }, + "lastModified": "2025-07-04T11:01:02.100Z", + "imageLink": "http://localhost:7860/api/persistent-images/28b9e6036c5c41e2e239", + "linkId": "28b9e6036c5c41e2e239", + "lastImageUpdate": "2025-07-04T05:33:06.186Z" + }, + { + "id": "OqhnuHq1d1", + "elements": [ + { + "type": "chart", + "id": "fapsuMydzF", + "chartType": "pie", + "left": 590.9448466970591, + "top": 11.526213462054173, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "kKCafi7Kr-", + "chartType": "ring", + "left": 78.31411459730043, + "top": 11.526213462054173, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "shape", + "id": "As7juQUqOf", + "left": 489.2459440894, + "top": 90.6035835451689, + "width": 86.70778582283008, + "height": 103.69178510771431, + "viewBox": [ + 86.70778582283008, + 103.69178510771431 + ], + "path": "M 32.51541968356128 0 L 32.51541968356128 41.007419326003394 L 0 41.007419326003394 L 0 62.684365781710916 L 32.51541968356128 62.684365781710916 L 32.51541968356128 103.69178510771431 L 54.1923661392688 103.69178510771431 L 54.1923661392688 62.684365781710916 L 86.70778582283008 62.684365781710916 L 86.70778582283008 41.007419326003394 L 54.1923661392688 41.007419326003394 L 54.1923661392688 0 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0, + "pathFormula": "plus", + "keypoints": [ + 0.25 + ] + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "lastModified": "2025-07-04T05:27:10.524Z", + "imageLink": "http://localhost:7860/api/persistent-images/871392505da1a438ec3e", + "linkId": "871392505da1a438ec3e", + "lastImageUpdate": "2025-07-04T05:27:10.524Z" + }, + { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 88.96882494004794, + "top": 39.0437649880096, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "chart", + "id": "y7tMDVxjp4", + "chartType": "area", + "left": 522.6725661724142, + "top": 10.298310647256827, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 2, + "props": { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 88.96882494004794, + "top": 39.0437649880096, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "chart", + "id": "y7tMDVxjp4", + "chartType": "area", + "left": 469.46442845723425, + "top": -17.870703437250214, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 2, + "props": { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 88.96882494004794, + "top": 39.0437649880096, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "chart", + "id": "y7tMDVxjp4", + "chartType": "area", + "left": 469.46442845723425, + "top": -17.870703437250214, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 2, + "props": { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 280.81534772182255, + "top": 10.266786570743406, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 2, + "props": { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 280.81534772182255, + "top": 10.266786570743406, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 2, + "props": { + "id": "qV-X4UilCE", + "elements": [ + { + "type": "chart", + "id": "7OLs5-I1R4", + "chartType": "radar", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "imageLink": "/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-03T12:43:54.560Z" + }, + "imageLink": "/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-03T12:55:45.593Z" + }, + "imageLink": "/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-03T12:56:19.084Z" + }, + "imageLink": "/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-03T12:57:00.140Z" + }, + "imageLink": "/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-03T12:57:03.071Z" + }, + "imageLink": "http://localhost:7860/api/persistent-images/ce39926d0d37d02ba7da", + "linkId": "ce39926d0d37d02ba7da", + "lastImageUpdate": "2025-07-04T06:18:44.265Z", + "lastModified": "2025-07-04T06:18:44.265Z" + }, + { + "id": "arO5qMy33M", + "elements": [ + { + "type": "chart", + "id": "x3DoEKHzUZ", + "chartType": "bar", + "left": 489.1178073034065, + "top": 61.346381474191475, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "IfrItmgpDa", + "left": 121.61490477726227, + "top": 119.24739741009097, + "width": 265.0874224478286, + "height": 129.72363226170336, + "viewBox": [ + 200, + 200 + ], + "path": "M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z", + "fill": "#90cf5b", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 4, + "props": { + "id": "arO5qMy33M", + "elements": [ + { + "type": "chart", + "id": "x3DoEKHzUZ", + "chartType": "bar", + "left": 482.3651062229743, + "top": -91.71484301560446, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "IfrItmgpDa", + "left": 574.2646841474844, + "top": 18.982656514382363, + "width": 265.0874224478286, + "height": 129.72363226170336, + "viewBox": [ + 200, + 200 + ], + "path": "M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z", + "fill": "#90cf5b", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 4, + "props": { + "id": "arO5qMy33M", + "elements": [ + { + "type": "chart", + "id": "x3DoEKHzUZ", + "chartType": "bar", + "left": 482.3651062229743, + "top": -91.71484301560446, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "IfrItmgpDa", + "left": 574.2646841474844, + "top": 18.982656514382363, + "width": 265.0874224478286, + "height": 129.72363226170336, + "viewBox": [ + 200, + 200 + ], + "path": "M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z", + "fill": "#90cf5b", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 4, + "props": { + "id": "arO5qMy33M", + "elements": [ + { + "type": "chart", + "id": "x3DoEKHzUZ", + "chartType": "bar", + "left": 482.3651062229743, + "top": -91.71484301560446, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "IfrItmgpDa", + "left": 574.2646841474844, + "top": 18.982656514382363, + "width": 265.0874224478286, + "height": 129.72363226170336, + "viewBox": [ + 200, + 200 + ], + "path": "M 100 0 A 100 100 102 1 0 200 100 L 100 100 L 100 0 Z", + "fill": "#90cf5b", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "imageLink": "/api/persistent-images/c4f2cb99960eb6ccd556", + "linkId": "c4f2cb99960eb6ccd556", + "lastImageUpdate": "2025-07-03T12:55:46.115Z" + }, + "imageLink": "/api/persistent-images/c4f2cb99960eb6ccd556", + "linkId": "c4f2cb99960eb6ccd556", + "lastImageUpdate": "2025-07-03T12:56:19.598Z" + }, + "imageLink": "/api/persistent-images/c4f2cb99960eb6ccd556", + "linkId": "c4f2cb99960eb6ccd556", + "lastImageUpdate": "2025-07-03T12:57:00.850Z" + }, + "imageLink": "http://localhost:7860/api/persistent-images/c4f2cb99960eb6ccd556", + "linkId": "c4f2cb99960eb6ccd556", + "lastImageUpdate": "2025-07-04T03:16:33.881Z", + "lastModified": "2025-07-04T11:01:32.591Z" + }, + { + "id": "tGq5X9TW40", + "elements": [ + { + "type": "chart", + "id": "UuD3PJWroL", + "chartType": "pie", + "left": -6.729912178548246, + "top": 60.23939731463558, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "FZ2HhxDw_f", + "chartType": "ring", + "left": 641.044254275091, + "top": 24.045002576618913, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "m6VFF4v-hF", + "chartType": "line", + "left": 323.8780211025765, + "top": 24.045002576618913, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "P_bdtJPnnA", + "left": 360.23354019631694, + "top": 99.89390044647868, + "width": 218.08610641097954, + "height": 152.28426395939087, + "viewBox": [ + 200, + 200 + ], + "path": "M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 5, + "props": { + "id": "tGq5X9TW40", + "elements": [ + { + "type": "chart", + "id": "UuD3PJWroL", + "chartType": "pie", + "left": -8.328633201729701, + "top": -48.47363226170336, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "FZ2HhxDw_f", + "chartType": "ring", + "left": 634.6493701823651, + "top": -40.95342169580749, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "m6VFF4v-hF", + "chartType": "line", + "left": 320.68057905621356, + "top": -76.67442188381276, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "P_bdtJPnnA", + "left": 366.62842428904275, + "top": 47.18344613649181, + "width": 218.08610641097954, + "height": 152.28426395939087, + "viewBox": [ + 200, + 200 + ], + "path": "M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 5, + "props": { + "id": "tGq5X9TW40", + "elements": [ + { + "type": "chart", + "id": "UuD3PJWroL", + "chartType": "pie", + "left": -8.328633201729701, + "top": -48.47363226170336, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "FZ2HhxDw_f", + "chartType": "ring", + "left": 634.6493701823651, + "top": -40.95342169580749, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "m6VFF4v-hF", + "chartType": "line", + "left": 320.68057905621356, + "top": -76.67442188381276, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "P_bdtJPnnA", + "left": 366.62842428904275, + "top": 47.18344613649181, + "width": 218.08610641097954, + "height": 152.28426395939087, + "viewBox": [ + 200, + 200 + ], + "path": "M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 5, + "props": { + "id": "tGq5X9TW40", + "elements": [ + { + "type": "chart", + "id": "UuD3PJWroL", + "chartType": "pie", + "left": -8.328633201729701, + "top": -48.47363226170336, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "FZ2HhxDw_f", + "chartType": "ring", + "left": 634.6493701823651, + "top": -40.95342169580749, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "m6VFF4v-hF", + "chartType": "line", + "left": 320.68057905621356, + "top": -76.67442188381276, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "P_bdtJPnnA", + "left": 353.8386561035911, + "top": 263.28596127632056, + "width": 218.08610641097954, + "height": 152.28426395939087, + "viewBox": [ + 200, + 200 + ], + "path": "M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "slideIndex": 5, + "props": { + "id": "tGq5X9TW40", + "elements": [ + { + "type": "chart", + "id": "UuD3PJWroL", + "chartType": "pie", + "left": -8.328633201729701, + "top": -48.47363226170336, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "FZ2HhxDw_f", + "chartType": "ring", + "left": 634.6493701823651, + "top": -40.95342169580749, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "chart", + "id": "m6VFF4v-hF", + "chartType": "line", + "left": 320.68057905621356, + "top": -76.67442188381276, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "P_bdtJPnnA", + "left": 329.8578407558693, + "top": 65.04455440182015, + "width": 218.08610641097954, + "height": 152.28426395939087, + "viewBox": [ + 200, + 200 + ], + "path": "M 180 160 A 100 100 0 1 0 100 200 L 200 200 L 200 160 L 180 160 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "imageLink": "/api/persistent-images/e57cc686dc7338ecad0f", + "linkId": "e57cc686dc7338ecad0f", + "lastImageUpdate": "2025-07-03T12:54:50.371Z" + }, + "imageLink": "/api/persistent-images/e57cc686dc7338ecad0f", + "linkId": "e57cc686dc7338ecad0f", + "lastImageUpdate": "2025-07-03T12:55:46.381Z" + }, + "imageLink": "/api/persistent-images/e57cc686dc7338ecad0f", + "linkId": "e57cc686dc7338ecad0f", + "lastImageUpdate": "2025-07-03T12:56:19.862Z" + }, + "imageLink": "/api/persistent-images/e57cc686dc7338ecad0f", + "linkId": "e57cc686dc7338ecad0f", + "lastImageUpdate": "2025-07-03T12:57:01.118Z" + }, + "lastModified": "2025-07-04T06:59:47.708Z", + "imageLink": "http://localhost:7860/api/persistent-images/e57cc686dc7338ecad0f", + "linkId": "e57cc686dc7338ecad0f", + "lastImageUpdate": "2025-07-04T06:59:47.708Z" + }, + { + "id": "4Stthdu2Bf", + "elements": [ + { + "type": "chart", + "id": "ae4DLBuSci", + "chartType": "bar", + "left": 100.00000000000003, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + }, + { + "type": "shape", + "id": "EHHlWiQEH7", + "left": 548.521452790819, + "top": 148.23373109024521, + "width": 225.35211267605632, + "height": 231.61189358372457, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 0 L 0 200 L 200 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + }, + "lastModified": "2025-07-04T06:40:02.624Z", + "imageLink": "http://localhost:7860/api/persistent-images/4bd5d90af592b7c7926a", + "linkId": "4bd5d90af592b7c7926a", + "lastImageUpdate": "2025-07-04T06:40:02.624Z" + } + ], + "theme": { + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "fontColor": "#333333", + "fontName": "Microsoft YaHei", + "backgroundColor": "#ffffff", + "shadow": { + "h": 3, + "v": 3, + "blur": 2, + "color": "#808080" + }, + "outline": { + "width": 2, + "color": "#525252", + "style": "solid" + }, + "themeColor": "#d14424" + }, + "viewportSize": 1000, + "viewportRatio": 0.5625, + "createdAt": "2025-07-04T11:01:33.655Z", + "updatedAt": "2025-07-04T11:01:33.655Z" +} \ No newline at end of file diff --git a/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/meta.json b/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..45917b946daa9574d96e1b297ee66cc19c197299 --- /dev/null +++ b/backend/data/users/PS01/9848393a-f385-4889-9967-5c708e08fc70/meta.json @@ -0,0 +1,10 @@ +{ + "pptId": "9848393a-f385-4889-9967-5c708e08fc70", + "userId": "PS01", + "title": "未命名演示文稿", + "slidesCount": 7, + "createdAt": "2025-07-04T11:01:33.657Z", + "updatedAt": "2025-07-04T11:01:33.657Z", + "storageType": "huggingface", + "size": 31730 +} \ No newline at end of file diff --git a/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/data.json b/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/data.json new file mode 100644 index 0000000000000000000000000000000000000000..bf7e7ee4c5b031448a9f21052ef304d75a525cd8 --- /dev/null +++ b/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/data.json @@ -0,0 +1,233 @@ +{ + "id": "77330288-0b80-4c3b-987d-440b20282bc5", + "title": "我的第一个演示文稿", + "slides": [ + { + "id": "LOE0v9936R", + "elements": [ + { + "type": "shape", + "id": "fFtb6Sleeo", + "left": 298.34571832979475, + "top": 228.18250176928518, + "width": 254.77707006369425, + "height": 184.00566171266806, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 200 A 50 100 0 1 1 200 200 L 0 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "text", + "id": "3zg38cVJPA", + "left": 161.0967503692762, + "top": 111.39832594780896, + "width": 452.97882816346623, + "height": 73, + "content": "

啊发发213123trt


", + "rotate": 0, + "defaultFontName": "Microsoft YaHei", + "defaultColor": "#333333", + "vertical": false + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + } + }, + { + "id": "50c099a5-936d-42ad-8d3e-a19c2743d439", + "elements": [ + { + "type": "text", + "id": "322db8ac-f333-41e6-b1be-1c208568a760", + "left": 150, + "top": 200, + "width": 600, + "height": 44, + "content": "我的第一个演示文稿", + "fontSize": 32, + "fontName": "Microsoft YaHei", + "defaultColor": "#333333", + "bold": true, + "align": "center" + }, + { + "type": "shape", + "id": "ovduZH0AOU", + "left": 301.1751575630252, + "top": 231.3620760804322, + "width": 175.5702280912365, + "height": 134.30372148859544, + "viewBox": [ + 200, + 200 + ], + "path": "M 75 0 L 125 0 L 175 25 L 200 75 L 200 125 L 175 175 L 125 200 L 75 200 L 25 175 L 0 125 L 0 75 L 25 25 L 75 0 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "shape", + "id": "TlIfGHC2Mo", + "left": 571.2832007803121, + "top": 167.58656587635053, + "width": 187.57503001200482, + "height": 178.57142857142858, + "viewBox": [ + 200, + 200 + ], + "path": "M 100 0 A 100 100 102 1 0 200 100 L 200 0 L 100 0 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "chart", + "id": "_ReBO54FSX", + "chartType": "pie", + "left": 65.906362545018, + "top": 43.99999999999999, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + } + }, + { + "id": "gSrKgAIh6d", + "elements": [ + { + "type": "shape", + "id": "osFRYAN8kB", + "left": 198.55191730191729, + "top": 236.23382998383, + "width": 113.1901131901132, + "height": 78.54007854007854, + "viewBox": [ + 200, + 200 + ], + "path": "M 160 20 A 100 100 0 1 0 200 100 L 100 100 L 160 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "chart", + "id": "kVtmct5gRJ", + "chartType": "pie", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + } + } + ], + "theme": { + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "fontColor": "#333333", + "fontName": "Microsoft YaHei", + "backgroundColor": "#ffffff", + "shadow": { + "h": 3, + "v": 3, + "blur": 2, + "color": "#808080" + }, + "outline": { + "width": 2, + "color": "#525252", + "style": "solid" + }, + "themeColor": "#d14424" + }, + "viewportSize": 1000, + "viewportRatio": 0.5625, + "createdAt": "2025-07-02T06:26:15.784Z", + "updatedAt": "2025-07-02T06:26:15.784Z" +} \ No newline at end of file diff --git a/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/meta.json b/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..4fd54c8f66d8dd51bee674e1b571d5c8376bb667 --- /dev/null +++ b/backend/data/users/PS02/77330288-0b80-4c3b-987d-440b20282bc5/meta.json @@ -0,0 +1,10 @@ +{ + "pptId": "77330288-0b80-4c3b-987d-440b20282bc5", + "userId": "PS02", + "title": "我的第一个演示文稿", + "slidesCount": 3, + "createdAt": "2025-07-02T06:26:15.786Z", + "updatedAt": "2025-07-02T06:26:15.786Z", + "storageType": "huggingface", + "size": 3044 +} \ No newline at end of file diff --git a/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/data.json b/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/data.json new file mode 100644 index 0000000000000000000000000000000000000000..4f193d7114e368b75e74b900aec12dd964bc1684 --- /dev/null +++ b/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/data.json @@ -0,0 +1,369 @@ +{ + "id": "a1bb5630-c846-460f-9faa-3c9f36537604", + "title": "未命名演示文稿", + "slides": [ + { + "id": "test-slide-1", + "elements": [ + { + "type": "shape", + "id": "4cbRxp", + "left": 0, + "top": 200, + "width": 546, + "height": 362.5, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 0 L 0 200 L 200 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "opacity": 0.7, + "rotate": 0 + }, + { + "type": "shape", + "id": "ookHrf", + "left": 0, + "top": 0, + "width": 300, + "height": 320, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 0 L 0 200 L 200 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "flipV": true, + "rotate": 0 + }, + { + "type": "text", + "id": "idn7Mx", + "left": 355, + "top": 65.25, + "width": 450, + "height": 154.390625, + "lineHeight": 1.2, + "content": "

PPTist

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333" + }, + { + "type": "text", + "id": "7stmVP", + "left": 355, + "top": 253.25, + "width": 585, + "height": 56, + "content": "

基于 Vue 3.x + TypeScript 的在线演示文稿应用

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333" + }, + { + "type": "line", + "id": "FnpZs4", + "left": 361, + "top": 238, + "start": [ + 0, + 0 + ], + "end": [ + 549, + 0 + ], + "points": [ + "", + "" + ], + "color": "#5b9bd5", + "style": "solid", + "width": 2 + } + ], + "background": { + "type": "solid", + "color": "#ffffff" + } + }, + { + "id": "test-slide-2", + "elements": [ + { + "type": "text", + "id": "ptNnUJ", + "left": 145, + "top": 148, + "width": 711, + "height": 77.59375, + "lineHeight": 1.2, + "content": "

在此处添加标题

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333" + }, + { + "type": "text", + "id": "mRHvQN", + "left": 207.50000000000003, + "top": 249.84259259259264, + "width": 585, + "height": 56, + "content": "

在此处添加副标题

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333" + }, + { + "type": "line", + "id": "7CQDwc", + "left": 323.09259259259267, + "top": 238.33333333333334, + "start": [ + 0, + 0 + ], + "end": [ + 354.8148148148148, + 0 + ], + "points": [ + "", + "" + ], + "color": "#5b9bd5", + "style": "solid", + "width": 4 + }, + { + "type": "shape", + "id": "09wqWw", + "left": -27.648148148148138, + "top": 432.73148148148147, + "width": 1056.2962962962963, + "height": 162.96296296296296, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + } + ], + "background": { + "type": "solid", + "color": "#fff" + } + }, + { + "id": "test-slide-3", + "elements": [ + { + "type": "shape", + "id": "vSheCJ", + "left": 183.5185185185185, + "top": 175.5092592592593, + "width": 605.1851851851851, + "height": 185.18518518518516, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 0 L 200 0 L 200 200 L 0 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0 + }, + { + "type": "shape", + "id": "Mpwv7x", + "left": 211.29629629629628, + "top": 201.80555555555557, + "width": 605.1851851851851, + "height": 185.18518518518516, + "viewBox": [ + 200, + 200 + ], + "path": "M 0 0 L 200 0 L 200 200 L 0 200 Z", + "fill": "#5b9bd5", + "fixedRatio": false, + "rotate": 0, + "opacity": 0.7 + }, + { + "type": "text", + "id": "WQOTAp", + "left": 304.9074074074074, + "top": 198.10185185185182, + "width": 417.9629629629629, + "height": 140, + "content": "

感谢观看

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333", + "wordSpace": 5 + } + ], + "background": { + "type": "solid", + "color": "#fff" + } + }, + { + "id": "1rQIA4Be1z", + "elements": [ + { + "type": "chart", + "id": "SZfZFCXC9d", + "chartType": "pie", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "值" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ] + ] + } + }, + { + "type": "text", + "id": "SuXr5Im32C", + "left": 106.93623830625306, + "top": 61.34088298046939, + "width": 170.68767438043656, + "height": 44, + "content": "

发法sdf2131

", + "rotate": 0, + "defaultFontName": "", + "defaultColor": "#333", + "vertical": false + } + ], + "background": { + "type": "solid", + "color": "#fff" + } + }, + { + "id": "CBfO54Kchx", + "elements": [ + { + "type": "chart", + "id": "6M0avsXxPU", + "chartType": "radar", + "left": 300, + "top": 81.25, + "width": 400, + "height": 400, + "rotate": 0, + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "textColor": "#333", + "data": { + "labels": [ + "类别1", + "类别2", + "类别3", + "类别4", + "类别5" + ], + "legends": [ + "系列1", + "系列2" + ], + "series": [ + [ + 12, + 19, + 5, + 2, + 18 + ], + [ + 7, + 11, + 13, + 21, + 9 + ] + ] + } + } + ], + "background": { + "type": "solid", + "color": "#fff" + } + } + ], + "theme": { + "themeColors": [ + "#5b9bd5", + "#ed7d31", + "#a5a5a5", + "#ffc000", + "#4472c4", + "#70ad47" + ], + "fontColor": "#333", + "fontName": "", + "backgroundColor": "#fff", + "shadow": { + "h": 3, + "v": 3, + "blur": 2, + "color": "#808080" + }, + "outline": { + "width": 2, + "color": "#525252", + "style": "solid" + }, + "themeColor": "#d14424" + }, + "viewportSize": 1000, + "viewportRatio": 0.5625, + "createdAt": "2025-07-02T11:44:14.495Z", + "updatedAt": "2025-07-02T11:44:14.495Z" +} \ No newline at end of file diff --git a/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/meta.json b/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..095377f2280eeeafec3943bfa6ae3587067e5118 --- /dev/null +++ b/backend/data/users/PS02/a1bb5630-c846-460f-9faa-3c9f36537604/meta.json @@ -0,0 +1,10 @@ +{ + "pptId": "a1bb5630-c846-460f-9faa-3c9f36537604", + "userId": "PS02", + "title": "未命名演示文稿", + "slidesCount": 5, + "createdAt": "2025-07-02T11:44:14.497Z", + "updatedAt": "2025-07-02T11:44:14.497Z", + "storageType": "huggingface", + "size": 4603 +} \ No newline at end of file diff --git a/backend/health-check.sh b/backend/health-check.sh new file mode 100644 index 0000000000000000000000000000000000000000..cb64fe13ded8061b37c06006c4000ad88890fdc7 --- /dev/null +++ b/backend/health-check.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +echo "🔍 PPTist 后端系统状态检查..." + +# 检查后端依赖 +echo "📦 检查后端依赖..." +cd /app/backend +if [ -d "node_modules" ]; then + echo "✅ 后端依赖已安装" +else + echo "❌ 后端依赖缺失" + exit 1 +fi + +# 检查前端构建 +echo "🔨 检查前端构建..." +cd /app/frontend +if [ -d "dist" ]; then + echo "✅ 前端已构建" +else + echo "⚠️ 前端未构建,开始构建..." + npm run build + if [ $? -eq 0 ]; then + echo "✅ 前端构建完成" + else + echo "❌ 前端构建失败" + exit 1 + fi +fi + +# 检查环境变量 +echo "🔧 检查环境变量..." +if [ -n "$GITHUB_TOKEN" ]; then + echo "✅ GITHUB_TOKEN 已设置" +else + echo "⚠️ GITHUB_TOKEN 未设置,某些功能可能无法使用" +fi + +if [ -n "$GITHUB_REPOS" ]; then + echo "✅ GITHUB_REPOS: $GITHUB_REPOS" +else + echo "⚠️ GITHUB_REPOS 未设置,使用默认仓库" +fi + +# 检查端口 +echo "🌐 检查端口配置..." +PORT=${PORT:-7860} +echo "✅ 服务将在端口 $PORT 启动" + +echo "🎉 系统检查完成,准备启动服务..." \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..0fdab4b4f22f44780229edb0ff2131f777871ded --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,8522 @@ +{ + "name": "pptist-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pptist-backend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.7.9", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "express-rate-limit": "^7.4.1", + "helmet": "^8.0.0", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.3", + "sharp": "^0.33.0", + "uuid": "^10.0.0", + "winston": "^3.11.0" + }, + "devDependencies": { + "@babel/preset-env": "^7.23.9", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "nodemon": "^3.1.7", + "prettier": "^3.2.5", + "supertest": "^6.3.4" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz", + "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz", + "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz", + "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.7", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.27.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.10", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.27", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.43.0", + "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.177", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmmirror.com/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmmirror.com/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "6.3.4", + "resolved": "https://registry.npmmirror.com/supertest/-/supertest-6.3.4.tgz", + "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.1.2" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmmirror.com/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmmirror.com/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..e93ad375a117e3d75b9bb588a41682fe0cc1b5e6 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,59 @@ +{ + "name": "pptist-backend", + "version": "1.0.0", + "description": "PPTist Backend Server for Huggingface Space", + "main": "src/app.js", + "type": "module", + "scripts": { + "dev": "nodemon src/app.js", + "start": "node src/app.js", + "build": "echo 'No build step required'", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "test:browser": "node test-browser.js", + "lint": "eslint src/**/*.js", + "lint:fix": "eslint src/**/*.js --fix", + "format": "prettier --write src/**/*.js", + "health": "curl -f http://localhost:7860/api/health || exit 1", + "validate": "npm run lint && npm run test" + }, + "dependencies": { + "axios": "^1.7.9", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "express-rate-limit": "^7.4.1", + "helmet": "^8.0.0", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.3", + + "sharp": "^0.33.0", + "uuid": "^10.0.0", + "winston": "^3.11.0" + }, + "devDependencies": { + "@babel/preset-env": "^7.23.9", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "nodemon": "^3.1.7", + "prettier": "^3.2.5", + "supertest": "^6.3.4" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "jest": { + "testEnvironment": "node", + "transform": { + "^.+\\.js$": "babel-jest" + }, + "collectCoverageFrom": [ + "src/**/*.js", + "!src/app.js" + ] + } +} diff --git a/backend/src/__tests__/health.test.js b/backend/src/__tests__/health.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bb5a96097d05f4ece8fcbdc42e10213527f78c45 --- /dev/null +++ b/backend/src/__tests__/health.test.js @@ -0,0 +1,68 @@ +import request from 'supertest'; +import express from 'express'; +import healthRoutes from '../routes/health.js'; + +// 创建测试应用 +const app = express(); +app.use('/api', healthRoutes); + +describe('Health Routes', () => { + describe('GET /api/health', () => { + it('should return health status', async () => { + const response = await request(app) + .get('/api/health') + .expect('Content-Type', /json/); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('status'); + expect(response.body).toHaveProperty('timestamp'); + expect(response.body).toHaveProperty('uptime'); + expect(response.body).toHaveProperty('responseTime'); + expect(response.body).toHaveProperty('services'); + + // 检查服务状态结构 + expect(response.body.services).toHaveProperty('database'); + expect(response.body.services).toHaveProperty('browser'); + expect(response.body.services).toHaveProperty('storage'); + }); + + it('should include memory information', async () => { + const response = await request(app) + .get('/api/health'); + + expect(response.body).toHaveProperty('memory'); + expect(response.body.memory).toHaveProperty('used'); + expect(response.body.memory).toHaveProperty('total'); + expect(response.body.memory).toHaveProperty('system'); + + expect(typeof response.body.memory.used).toBe('number'); + expect(typeof response.body.memory.total).toBe('number'); + expect(typeof response.body.memory.system).toBe('number'); + }); + + it('should include environment information', async () => { + const response = await request(app) + .get('/api/health'); + + expect(response.body).toHaveProperty('version'); + expect(response.body).toHaveProperty('environment'); + }); + }); + + describe('GET /api/ping', () => { + it('should return pong response', async () => { + const response = await request(app) + .get('/api/ping') + .expect('Content-Type', /json/) + .expect(200); + + expect(response.body).toHaveProperty('message', 'pong'); + expect(response.body).toHaveProperty('timestamp'); + + // 验证时间戳格式 + const timestamp = new Date(response.body.timestamp); + expect(timestamp).toBeInstanceOf(Date); + expect(timestamp.getTime()).not.toBeNaN(); + }); + }); +}); \ No newline at end of file diff --git a/backend/src/app.js b/backend/src/app.js new file mode 100644 index 0000000000000000000000000000000000000000..378aeacc0987896a657810ee2bfd21401ddf0b8f --- /dev/null +++ b/backend/src/app.js @@ -0,0 +1,1060 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import rateLimit from 'express-rate-limit'; +import dotenv from 'dotenv'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import axios from 'axios'; +import fs from 'fs/promises'; // 添加ES模块fs导入 + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 首先加载环境变量 - 必须在导入服务之前 +dotenv.config({ path: path.join(__dirname, '../../.env') }); + +// 验证环境变量是否正确加载 +console.log('=== Environment Variables Check ==='); +console.log('GITHUB_TOKEN configured:', !!process.env.GITHUB_TOKEN); +console.log('GITHUB_TOKEN length:', process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0); +console.log('GITHUB_REPOS configured:', !!process.env.GITHUB_REPOS); +console.log('GITHUB_REPOS value:', process.env.GITHUB_REPOS); + +// 现在导入需要环境变量的模块 +import authRoutes from './routes/auth.js'; +import pptRoutes from './routes/ppt.js'; +import publicRoutes from './routes/public.js'; +import imagesRoutes from './routes/images.js'; +import persistentImagesRoutes from './routes/persistentImages.js'; +import persistentLinksRoutes from './routes/persistentLinks.js'; +import exportRoutes from './routes/export.js'; +import { authenticateToken } from './middleware/auth.js'; +import { errorHandler } from './middleware/errorHandler.js'; + +// 导入新的服务 +import huggingfaceStorageService from './services/huggingfaceStorageService.js'; +import backupSchedulerService from './services/backupSchedulerService.js'; +import persistentImageLinkService from './services/persistentImageLinkService.js'; +import autoBackupService from './services/autoBackupService.js'; +import githubService from './services/githubService.js'; + +const app = express(); +const PORT = process.env.PORT || 7861; // 修改为7861端口 + +// 设置trust proxy用于Huggingface Space +app.set('trust proxy', true); + +// 安全中间件 +app.use(helmet({ + contentSecurityPolicy: false, // 为了兼容前端静态文件 +})); + +// 修复限流配置 - 针对Huggingface Space生产环境 +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 100, // 每个IP每15分钟最多100个请求 + message: 'Too many requests from this IP, please try again later.', + trustProxy: true, // 与Express trust proxy设置保持一致 + validate: { + trustProxy: false // 禁用trust proxy验证以避免生产环境警告 + }, + standardHeaders: true, + legacyHeaders: false +}); + +// 应用限流中间件 +app.use('/api', limiter); + +// CORS配置 +app.use(cors({ + origin: process.env.FRONTEND_URL || '*', + credentials: true +})); + +app.use(express.json({ limit: '50mb' })); +app.use(express.urlencoded({ extended: true, limit: '50mb' })); + +// 提供前端静态文件 - 修复路径配置,避免与API路由冲突 +const frontendDistPath = path.join(__dirname, '../../frontend/dist'); +console.log('Frontend dist path:', frontendDistPath); + +// API路由 - 必须在静态文件服务之前注册 +console.log('Registering API routes...'); + +// 认证相关路由(不需要认证) +app.use('/api/auth', authRoutes); + +// 公共访问路由(不需要认证) +app.use('/api/public', publicRoutes); + +// 添加测试路由来验证 PPT 路由是否工作(不需要认证) +app.get('/api/ppt/test', (req, res) => { + res.json({ message: 'PPT routes are working', timestamp: new Date().toISOString() }); +}); + +// PPT管理路由(需要认证) +app.use('/api/ppt', (req, res, next) => { + console.log(`PPT route accessed: ${req.method} ${req.path}`); + next(); +}, authenticateToken, pptRoutes); + +// 图片管理路由(部分需要认证) +app.use('/api/images', imagesRoutes); + +// 持久化图片链接路由 +app.use('/api/persistent-images', persistentImagesRoutes); + +// 持久化链接管理路由(需要认证) +app.use('/api/persistent-links', persistentLinksRoutes); + +// 添加 /api/persistent/ 路由来处理前端生成的永久链接格式 +app.get('/api/persistent/:pptId/:slideId', async (req, res) => { + try { + const { pptId, slideId } = req.params; + console.log(`🔗 Accessing persistent link: pptId=${pptId}, slideId=${slideId}`); + + // 通过 pptId 和 slideId 查找对应的 linkId + const linkInfo = await persistentImageLinkService.findLinkByPptAndSlide(pptId, slideId); + + if (!linkInfo) { + console.log(`❌ No persistent link found for pptId=${pptId}, slideId=${slideId}`); + return res.status(404).json({ error: 'Persistent link not found' }); + } + + // 检查图片是否存在,如果不存在则生成占位图片 + let imageResult; + try { + imageResult = await persistentImageLinkService.getPersistentImage(linkInfo.linkId); + } catch (error) { + if (error.message.includes('Image not available')) { + console.log(`⚠️ Image not available for linkId=${linkInfo.linkId}, generating placeholder`); + // 生成占位图片 + const placeholderBuffer = await persistentImageLinkService.generatePlaceholderImage({ + width: 1920, + height: 1080, + format: 'jpeg' + }); + + // 保存占位图片到文件系统 + const imagePath = path.join(persistentImageLinkService.linksDir, `${linkInfo.linkId}.jpeg`); + await fs.writeFile(imagePath, placeholderBuffer); + + // 更新链接信息 + linkInfo.hasImage = true; + linkInfo.imagePath = imagePath; + linkInfo.format = 'jpeg'; + linkInfo.imageSize = placeholderBuffer.length; + linkInfo.lastUpdated = new Date().toISOString(); + + // 保存更新后的链接映射 + await persistentImageLinkService.saveLinkMap(); + + imageResult = { + success: true, + data: placeholderBuffer, + metadata: { + linkId: linkInfo.linkId, + format: 'jpeg', + size: placeholderBuffer.length, + createdAt: linkInfo.createdAt, + lastUpdated: linkInfo.lastUpdated + } + }; + } else { + throw error; + } + } + + if (!imageResult || !imageResult.success) { + console.log(`❌ Failed to get image for linkId=${linkInfo.linkId}`); + return res.status(404).json({ error: 'Image not found' }); + } + + // 设置正确的 Content-Type + const format = imageResult.metadata?.format || linkInfo.format || 'jpeg'; + res.setHeader('Content-Type', `image/${format}`); + res.setHeader('Cache-Control', 'public, max-age=31536000'); // 缓存1年 + + console.log(`✅ Serving persistent image: linkId=${linkInfo.linkId}, format=${format}`); + res.send(imageResult.data); + + } catch (error) { + console.error('❌ Error serving persistent image:', error); + res.status(500).json({ error: 'Internal server error', message: error.message }); + } +}); + +// 导出功能路由 +app.use('/api/export', exportRoutes); + +console.log('All API routes registered successfully'); + +// 添加调试中间件 - 处理未匹配的API路由 +app.use('/api/*', (req, res) => { + console.log(`Unmatched API route: ${req.method} ${req.path}`); + res.status(404).json({ error: 'API route not found', path: req.path }); +}); + +// 静态文件服务 - 在API路由之后 +app.use((req, res, next) => { + // 如果请求路径以 /api/ 开头,跳过静态文件服务 + if (req.path.startsWith('/api/')) { + return next(); + } + // 否则使用静态文件服务 + express.static(frontendDistPath)(req, res, next); +}); + +// 提供数据文件 +app.use('/data', express.static(path.join(__dirname, '../../frontend/public/mocks'))); + +// 健康检查 - 需要在其他路由之前 +app.get('/api/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// GitHub连接状态检查 +app.get('/api/github/status', async (req, res) => { + try { + const { default: githubService } = await import('./services/githubService.js'); + const validation = await githubService.validateConnection(); + res.json({ + github: validation, + environment: { + tokenConfigured: !!process.env.GITHUB_TOKEN, + tokenPreview: process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN.substring(0, 8)}...` : 'Not set', + reposConfigured: !!process.env.GITHUB_REPOS, + reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], + nodeEnv: process.env.NODE_ENV + } + }); + } catch (error) { + res.status(500).json({ + error: error.message, + stack: error.stack + }); + } +}); + +// 添加GitHub测试路由 +app.get('/api/github/test', async (req, res) => { + try { + console.log('=== GitHub Connection Test ==='); + console.log('GITHUB_TOKEN exists:', !!process.env.GITHUB_TOKEN); + console.log('GITHUB_REPOS:', process.env.GITHUB_REPOS); + + const { default: githubService } = await import('./services/githubService.js'); + + // 测试基本配置 + const config = { + hasToken: !!githubService.token, + useMemoryStorage: githubService.useMemoryStorage, + repositories: githubService.repositories, + apiUrl: githubService.apiUrl + }; + + console.log('GitHub Service Config:', config); + + // 如果有token,测试连接 + let connectionTest = null; + if (githubService.token) { + console.log('Testing GitHub API connection...'); + connectionTest = await githubService.validateConnection(); + console.log('Connection test result:', connectionTest); + } + + res.json({ + timestamp: new Date().toISOString(), + config, + connectionTest, + environment: { + tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, + nodeEnv: process.env.NODE_ENV + } + }); + + } catch (error) { + console.error('GitHub test error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加GitHub调试路由 +app.get('/api/debug/github', async (req, res) => { + try { + console.log('=== GitHub Debug Information ==='); + + const { default: githubService } = await import('./services/githubService.js'); + + const debugInfo = { + timestamp: new Date().toISOString(), + environment: { + tokenConfigured: !!process.env.GITHUB_TOKEN, + tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, + reposConfigured: !!process.env.GITHUB_REPOS, + reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], + nodeEnv: process.env.NODE_ENV + }, + service: { + hasToken: !!githubService.token, + repositoriesCount: githubService.repositories?.length || 0, + repositories: githubService.repositories || [], + apiUrl: githubService.apiUrl + } + }; + + // 测试连接 + try { + const connectionTest = await githubService.validateConnection(); + debugInfo.connectionTest = connectionTest; + } catch (connError) { + debugInfo.connectionError = connError.message; + } + + console.log('Debug info:', debugInfo); + res.json(debugInfo); + + } catch (error) { + console.error('Debug route error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加PPT调试路由 +app.get('/api/debug/ppt/:userId', async (req, res) => { + try { + const { userId } = req.params; + console.log(`=== PPT Debug for User: ${userId} ===`); + + const { default: githubService } = await import('./services/githubService.js'); + + const debugInfo = { + timestamp: new Date().toISOString(), + userId: userId, + repositories: [] + }; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + return res.json({ + ...debugInfo, + error: 'GitHub repositories not configured', + details: 'No GitHub repositories available for PPT storage' + }); + } + + // 检查每个仓库中用户的文件 + for (let i = 0; i < githubService.repositories.length; i++) { + const repoInfo = { + index: i, + url: githubService.repositories[i], + accessible: false, + userDirectoryExists: false, + files: [] + }; + + try { + const { owner, repo } = githubService.parseRepoUrl(githubService.repositories[i]); + + // 检查仓库是否可访问 + await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + } + }); + repoInfo.accessible = true; + + // 检查用户目录 + try { + const userDirResponse = await axios.get( + `https://api.github.com/repos/${owner}/${repo}/contents/users/${userId}`, + { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + } + } + ); + + repoInfo.userDirectoryExists = true; + repoInfo.files = userDirResponse.data + .filter(item => item.type === 'file' && item.name.endsWith('.json')) + .map(file => ({ + name: file.name, + size: file.size, + sha: file.sha + })); + } catch (userDirError) { + repoInfo.userDirectoryError = userDirError.response?.status === 404 ? 'Directory not found' : userDirError.message; + } + + } catch (repoError) { + repoInfo.error = repoError.message; + } + + debugInfo.repositories.push(repoInfo); + } + + console.log('PPT Debug info:', debugInfo); + res.json(debugInfo); + + } catch (error) { + console.error('PPT Debug route error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加仓库初始化端点 +app.post('/api/github/initialize', async (req, res) => { + try { + console.log('=== Manual Repository Initialization ==='); + + const { default: githubService } = await import('./services/githubService.js'); + + if (githubService.useMemoryStorage) { + return res.status(400).json({ + error: 'Cannot initialize repository: using memory storage mode', + reason: 'GitHub token not configured' + }); + } + + const { repoIndex = 0 } = req.body; + console.log(`Initializing repository at index: ${repoIndex}`); + + const result = await githubService.initializeRepository(repoIndex); + + if (result.success) { + res.json({ + success: true, + message: 'Repository initialized successfully', + commit: result.commit, + timestamp: new Date().toISOString() + }); + } else { + res.status(500).json({ + success: false, + error: result.error, + reason: result.reason + }); + } + + } catch (error) { + console.error('Repository initialization error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加GitHub权限详细检查路由 +app.get('/api/debug/github-permissions', async (req, res) => { + try { + console.log('=== GitHub Token Permissions Check ==='); + + const { default: githubService } = await import('./services/githubService.js'); + + if (!githubService.token) { + return res.status(400).json({ error: 'No GitHub token configured' }); + } + + const debugInfo = { + timestamp: new Date().toISOString(), + tokenInfo: {}, + repositoryTests: [] + }; + + // 1. 检查token基本信息和权限 + try { + const userResponse = await axios.get('https://api.github.com/user', { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + } + }); + + debugInfo.tokenInfo = { + login: userResponse.data.login, + id: userResponse.data.id, + type: userResponse.data.type, + company: userResponse.data.company, + publicRepos: userResponse.data.public_repos, + privateRepos: userResponse.data.total_private_repos, + tokenScopes: userResponse.headers['x-oauth-scopes'] || 'Unknown' + }; + + console.log('Token info:', debugInfo.tokenInfo); + + } catch (tokenError) { + debugInfo.tokenError = { + status: tokenError.response?.status, + message: tokenError.message + }; + } + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + debugInfo.repositoryError = 'GitHub repositories not configured'; + return res.json(debugInfo); + } + + // 2. 检查每个仓库的详细状态 + for (let i = 0; i < githubService.repositories.length; i++) { + const repoUrl = githubService.repositories[i]; + const repoTest = { + index: i, + url: repoUrl, + tests: {} + }; + + try { + const { owner, repo } = githubService.parseRepoUrl(repoUrl); + repoTest.owner = owner; + repoTest.repo = repo; + + // 测试1: 基本仓库访问 + try { + const repoResponse = await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + } + }); + + repoTest.tests.basicAccess = { + success: true, + repoExists: true, + private: repoResponse.data.private, + permissions: repoResponse.data.permissions, + defaultBranch: repoResponse.data.default_branch, + size: repoResponse.data.size + }; + + } catch (repoError) { + repoTest.tests.basicAccess = { + success: false, + status: repoError.response?.status, + message: repoError.message, + details: repoError.response?.data + }; + + // 如果是404,检查是否是权限问题还是仓库不存在 + if (repoError.response?.status === 404) { + // 尝试不使用认证访问(如果是公开仓库应该能访问) + try { + await axios.get(`https://api.github.com/repos/${owner}/${repo}`); + repoTest.tests.basicAccess.possibleCause = 'Repository exists but token lacks permission'; + } catch (publicError) { + if (publicError.response?.status === 404) { + repoTest.tests.basicAccess.possibleCause = 'Repository does not exist'; + } + } + } + } + + // 测试2: 检查是否能列出用户的仓库 + try { + const userReposResponse = await axios.get(`https://api.github.com/users/${owner}/repos?per_page=100`, { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + } + }); + + const hasRepo = userReposResponse.data.some(r => r.name === repo); + repoTest.tests.userReposList = { + success: true, + totalRepos: userReposResponse.data.length, + targetRepoFound: hasRepo, + repoNames: userReposResponse.data.slice(0, 10).map(r => ({ name: r.name, private: r.private })) + }; + + } catch (userReposError) { + repoTest.tests.userReposList = { + success: false, + status: userReposError.response?.status, + message: userReposError.message + }; + } + + } catch (parseError) { + repoTest.parseError = parseError.message; + } + + debugInfo.repositoryTests.push(repoTest); + } + + // 3. 提供修复建议 + const suggestions = []; + + if (debugInfo.tokenError) { + suggestions.push('Token authentication failed - check if GITHUB_TOKEN is valid'); + } + + debugInfo.repositoryTests.forEach((repoTest, index) => { + if (!repoTest.tests.basicAccess?.success) { + if (repoTest.tests.basicAccess?.status === 404) { + if (repoTest.tests.basicAccess?.possibleCause === 'Repository does not exist') { + suggestions.push(`Repository ${repoTest.url} does not exist - please create it on GitHub`); + } else { + suggestions.push(`Repository ${repoTest.url} exists but token lacks permission - check token scopes`); + } + } else if (repoTest.tests.basicAccess?.status === 403) { + suggestions.push(`Permission denied for ${repoTest.url} - check if token has 'repo' scope`); + } + } + }); + + debugInfo.suggestions = suggestions; + + console.log('GitHub permissions debug completed'); + res.json(debugInfo); + + } catch (error) { + console.error('GitHub permissions check error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加大文件处理调试端点 +app.get('/api/debug/large-files/:userId', async (req, res) => { + try { + const { userId } = req.params; + console.log(`=== Large Files Debug for User: ${userId} ===`); + + const { default: githubService } = await import('./services/githubService.js'); + + const debugInfo = { + timestamp: new Date().toISOString(), + userId: userId, + fileAnalysis: [] + }; + + // 检查用户的所有PPT文件 + const pptList = await githubService.getUserPPTList(userId); + + for (const ppt of pptList) { + const fileInfo = { + pptId: ppt.name, + title: ppt.title, + fileName: `${ppt.name}.json`, + analysis: {} + }; + + try { + // 获取文件内容 + const result = await githubService.getFile(userId, `${ppt.name}.json`, ppt.repoIndex || 0); + + if (result && result.content) { + const content = result.content; + const jsonString = JSON.stringify(content); + const fileSize = Buffer.byteLength(jsonString, 'utf8'); + + fileInfo.analysis = { + fileSize: fileSize, + fileSizeKB: (fileSize / 1024).toFixed(2), + slidesCount: content.slides?.length || 0, + isChunked: !!content.isChunked, + chunkedInfo: content.isChunked ? { + totalChunks: content.totalChunks, + totalSlides: content.totalSlides + } : null, + wasReassembled: !!result.isReassembled, + metadata: content.metadata || 'No metadata', + status: fileSize > 1024 * 1024 ? 'LARGE' : fileSize > 800 * 1024 ? 'MEDIUM' : 'NORMAL' + }; + + // 分析每个slide的大小 + if (content.slides && content.slides.length > 0) { + const slideSizes = content.slides.map((slide, index) => { + const slideJson = JSON.stringify(slide); + const slideSize = Buffer.byteLength(slideJson, 'utf8'); + return { + index: index, + size: slideSize, + sizeKB: (slideSize / 1024).toFixed(2), + elementsCount: slide.elements?.length || 0 + }; + }); + + // 找出最大的slides + const largestSlides = slideSizes + .sort((a, b) => b.size - a.size) + .slice(0, 3); + + fileInfo.analysis.slideSummary = { + averageSlideSize: (fileSize / content.slides.length).toFixed(0), + largestSlides: largestSlides + }; + } + } else { + fileInfo.analysis = { error: 'Could not read file content' }; + } + + } catch (error) { + fileInfo.analysis = { + error: error.message, + errorType: error.name + }; + } + + debugInfo.fileAnalysis.push(fileInfo); + } + + // 添加统计摘要 + debugInfo.summary = { + totalFiles: debugInfo.fileAnalysis.length, + largeFiles: debugInfo.fileAnalysis.filter(f => f.analysis.status === 'LARGE').length, + chunkedFiles: debugInfo.fileAnalysis.filter(f => f.analysis.isChunked).length, + errors: debugInfo.fileAnalysis.filter(f => f.analysis.error).length + }; + + console.log('Large files debug completed'); + res.json(debugInfo); + + } catch (error) { + console.error('Large files debug error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加分块文件修复端点 +app.post('/api/debug/fix-chunked-file/:userId/:pptId', async (req, res) => { + try { + const { userId, pptId } = req.params; + console.log(`=== Fixing Chunked File: ${userId}/${pptId} ===`); + + const { default: githubService } = await import('./services/githubService.js'); + const fileName = `${pptId}.json`; + + const result = { + timestamp: new Date().toISOString(), + userId, + pptId, + fileName, + status: 'unknown', + details: {}, + actions: [] + }; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + return res.status(500).json({ + ...result, + status: 'error', + error: 'GitHub repositories not configured' + }); + } + + // 1. 检查主文件是否存在 + let mainFile = null; + let mainFileRepo = -1; + + for (let i = 0; i < githubService.repositories.length; i++) { + try { + const fileResult = await githubService.getFile(userId, fileName, i); + if (fileResult) { + mainFile = fileResult; + mainFileRepo = i; + result.details.mainFileFound = true; + result.details.mainFileRepo = i; + result.actions.push(`Main file found in repository ${i}`); + break; + } + } catch (error) { + continue; + } + } + + if (!mainFile) { + result.status = 'error'; + result.details.error = 'Main file not found in any repository'; + return res.json(result); + } + + const content = mainFile.content; + + // 2. 检查是否是分块文件 + if (!content.isChunked) { + result.status = 'normal'; + result.details.isChunked = false; + result.details.slideCount = content.slides?.length || 0; + result.actions.push('File is not chunked, no action needed'); + return res.json(result); + } + + // 3. 分析分块文件状态 + result.details.isChunked = true; + result.details.totalChunks = content.totalChunks; + result.details.totalSlides = content.totalSlides; + result.details.mainFileSlides = content.slides?.length || 0; + + // 4. 检查所有chunk文件 + const chunkStatus = []; + let totalFoundSlides = content.slides?.length || 0; + + for (let i = 1; i < content.totalChunks; i++) { + const chunkFileName = fileName.replace('.json', `_chunk_${i}.json`); + const chunkInfo = { + index: i, + fileName: chunkFileName, + found: false, + slides: 0, + error: null + }; + + try { + const repoUrl = githubService.repositories[mainFileRepo]; + const { owner, repo } = githubService.parseRepoUrl(repoUrl); + const path = `users/${userId}/${chunkFileName}`; + + const response = await axios.get( + `${githubService.apiUrl}/repos/${owner}/${repo}/contents/${path}`, + { + headers: { + 'Authorization': `token ${githubService.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + + const chunkContent = Buffer.from(response.data.content, 'base64').toString('utf8'); + const chunkData = JSON.parse(chunkContent); + + chunkInfo.found = true; + chunkInfo.slides = chunkData.slides?.length || 0; + totalFoundSlides += chunkInfo.slides; + + result.actions.push(`Chunk ${i} found: ${chunkInfo.slides} slides`); + } catch (error) { + chunkInfo.error = error.message; + result.actions.push(`Chunk ${i} missing or error: ${error.message}`); + } + + chunkStatus.push(chunkInfo); + } + + result.details.chunks = chunkStatus; + result.details.totalFoundSlides = totalFoundSlides; + result.details.missingSlides = content.totalSlides - totalFoundSlides; + + // 5. 判断状态和建议修复方案 + const missingChunks = chunkStatus.filter(chunk => !chunk.found); + + if (missingChunks.length === 0 && totalFoundSlides === content.totalSlides) { + result.status = 'healthy'; + result.actions.push('All chunks found, file should load correctly'); + } else if (missingChunks.length > 0) { + result.status = 'incomplete'; + result.details.missingChunks = missingChunks.map(c => c.index); + result.actions.push(`Missing chunks: ${missingChunks.map(c => c.index).join(', ')}`); + + // 提供修复建议 + if (totalFoundSlides >= content.totalSlides * 0.8) { + result.actions.push('Recommendation: Reassemble available slides into single file'); + result.details.recommendation = 'reassemble'; + } else { + result.actions.push('Recommendation: File may be corrupted, consider restoration from backup'); + result.details.recommendation = 'restore'; + } + } else { + result.status = 'mismatch'; + result.actions.push('Slide count mismatch detected'); + } + + console.log('Chunked file analysis completed:', result); + res.json(result); + + } catch (error) { + console.error('Chunked file fix error:', error); + res.status(500).json({ + error: error.message, + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); + } +}); + +// 添加分块文件重组端点 +app.post('/api/debug/reassemble-chunked-file/:userId/:pptId', async (req, res) => { + try { + const { userId, pptId } = req.params; + console.log(`=== Reassembling Chunked File: ${userId}/${pptId} ===`); + + const { default: githubService } = await import('./services/githubService.js'); + const fileName = `${pptId}.json`; + + // 强制重新组装文件 + const result = await githubService.getFile(userId, fileName, 0); + + if (!result) { + return res.status(404).json({ error: 'File not found' }); + } + + if (result.isReassembled) { + res.json({ + success: true, + message: 'File reassembled successfully', + slideCount: result.content.slides?.length || 0, + wasChunked: !!result.content.reassembledInfo, + reassembledInfo: result.content.reassembledInfo + }); + } else { + res.json({ + success: true, + message: 'File was not chunked', + slideCount: result.content.slides?.length || 0, + wasChunked: false + }); + } + + } catch (error) { + console.error('File reassembly error:', error); + res.status(500).json({ + error: error.message, + details: error.stack + }); + } +}); + +// 添加路由注册日志 +console.log('Importing route modules...'); +console.log('Auth routes imported:', !!authRoutes); +console.log('PPT routes imported:', !!pptRoutes); +console.log('Public routes imported:', !!publicRoutes); + +// 这些路由已经在前面注册过了,删除重复的注册 + +// 前端路由处理 - 修复ES模块兼容性 +app.get('*', (req, res) => { + const indexPath = path.join(frontendDistPath, 'index.html'); + console.log(`Serving frontend route: ${req.path}, index.html path: ${indexPath}`); + + // 使用ES模块的fs检查文件是否存在 + if (fs.existsSync(indexPath)) { + res.sendFile(indexPath); + } else { + console.error('index.html not found at:', indexPath); + res.status(404).send(` +

前端文件未找到

+

index.html路径: ${indexPath}

+

请确保前端已正确构建

+ 访问API测试页面 + `); + } +}); + +// 错误处理中间件 +app.use(errorHandler); + +// 初始化服务 +async function initializeServices() { + console.log('🚀 Initializing services...'); + + let servicesInitialized = 0; + let totalServices = 5; + + // 初始化GitHub服务 + try { + await githubService.initialize(); + servicesInitialized++; + } catch (error) { + console.error('❌ Failed to initialize GitHubService:', error); + console.log('⚠️ Continuing without GitHubService...'); + } + + // 初始化Huggingface存储服务 + try { + await huggingfaceStorageService.initialize(); + servicesInitialized++; + } catch (error) { + console.error('❌ Failed to initialize HuggingfaceStorageService:', error); + console.log('⚠️ Continuing without HuggingfaceStorageService...'); + } + + // 初始化备份调度服务 + try { + await backupSchedulerService.initialize(); + servicesInitialized++; + } catch (error) { + console.error('❌ Failed to initialize BackupSchedulerService:', error); + console.log('⚠️ Continuing without BackupSchedulerService...'); + } + + // 初始化持久化图片链接服务 + try { + await persistentImageLinkService.initialize(); + servicesInitialized++; + } catch (error) { + console.error('❌ Failed to initialize PersistentImageLinkService:', error); + console.log('⚠️ Continuing without PersistentImageLinkService...'); + } + + // 初始化自动备份服务 + try { + await autoBackupService.initialize(); + servicesInitialized++; + } catch (error) { + console.error('❌ Failed to initialize AutoBackupService:', error); + console.log('⚠️ Continuing without AutoBackupService...'); + } + + console.log(`✅ ${servicesInitialized}/${totalServices} services initialized successfully`); + + if (servicesInitialized === 0) { + console.error('❌ No services could be initialized. Exiting...'); + process.exit(1); + } +} + +// 启动服务器 +async function startServer() { + await initializeServices(); + + app.listen(PORT, '0.0.0.0', () => { + console.log(`Server is running on port ${PORT}`); + console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); + console.log(`💾 Huggingface storage service ready`); + console.log(`⏰ Backup scheduler service ready`); + console.log(`🔗 Persistent image link service ready`); + console.log(`🔄 Auto backup service ready (every 8 hours)`); + }); +} + +// 启动服务器 +startServer().catch(error => { + console.error('❌ Failed to start server:', error); + process.exit(1); +}); + +// 优雅关闭处理 +process.on('SIGTERM', async () => { + console.log('🛑 Received SIGTERM, shutting down gracefully...'); + await backupSchedulerService.cleanup(); + await autoBackupService.cleanup(); + process.exit(0); +}); + +process.on('SIGINT', async () => { + console.log('🛑 Received SIGINT, shutting down gracefully...'); + await backupSchedulerService.cleanup(); + await autoBackupService.cleanup(); + process.exit(0); +}); \ No newline at end of file diff --git a/backend/src/config/users.js b/backend/src/config/users.js new file mode 100644 index 0000000000000000000000000000000000000000..4348ebb809124e9ba4e5c181eeaacce50ef90f07 --- /dev/null +++ b/backend/src/config/users.js @@ -0,0 +1,41 @@ +// 内置用户配置 +export const USERS = [ + { + id: 'PS01', + username: 'PS01', + password: 'admin_cybercity2025', + role: 'admin' + }, + { + id: 'PS02', + username: 'PS02', + password: 'cybercity2025', + role: 'user' + }, + { + id: 'PS03', + username: 'PS03', + password: 'cybercity2025', + role: 'user' + }, + { + id: 'PS04', + username: 'PS04', + password: 'cybercity2025', + role: 'user' + } +]; + +// JWT配置 - 可选,仅在需要认证时使用 +export const JWT_SECRET = process.env.JWT_SECRET || null; +export const JWT_EXPIRES_IN = '24h'; +export const JWT_ENABLED = !!process.env.JWT_SECRET; + +// 修复GitHub配置,去除多余空格 +export const GITHUB_CONFIG = { + apiUrl: 'https://api.github.com', + token: process.env.GITHUB_TOKEN, + repositories: process.env.GITHUB_REPOS + ? process.env.GITHUB_REPOS.split(',').map(repo => repo.trim()) // 去除每个仓库URL的空格 + : [] +}; \ No newline at end of file diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..9046d0b0d0a056e5703f2e4520b6ef99e97972e4 --- /dev/null +++ b/backend/src/middleware/auth.js @@ -0,0 +1,37 @@ +import jwt from 'jsonwebtoken'; +import { JWT_SECRET, JWT_ENABLED } from '../config/users.js'; + +export const authenticateToken = (req, res, next) => { + console.log(`Authenticating request: ${req.method} ${req.path}`); + + // 如果JWT未启用,提供默认用户并跳过验证 + if (!JWT_ENABLED) { + console.log('JWT disabled, using default user'); + req.user = { + userId: 'PS01', + username: 'PS01', + role: 'admin' + }; + return next(); + } + + console.log('Authorization header:', req.headers['authorization'] ? 'Present' : 'Missing'); + + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + console.log('No token provided'); + return res.status(401).json({ error: 'Access token required' }); + } + + jwt.verify(token, JWT_SECRET, (err, user) => { + if (err) { + console.log('Token verification failed:', err.message); + return res.status(403).json({ error: 'Invalid or expired token' }); + } + console.log('Token verified for user:', user.userId); + req.user = user; + next(); + }); +}; \ No newline at end of file diff --git a/backend/src/middleware/errorHandler.js b/backend/src/middleware/errorHandler.js new file mode 100644 index 0000000000000000000000000000000000000000..eca747d505320e4c2677c84f78148580729df86d --- /dev/null +++ b/backend/src/middleware/errorHandler.js @@ -0,0 +1,101 @@ +export const errorHandler = (err, req, res, next) => { + console.error('Error:', err); + + // JWT错误 + if (err.name === 'JsonWebTokenError') { + return res.status(401).json({ error: 'Invalid token' }); + } + + if (err.name === 'TokenExpiredError') { + return res.status(401).json({ error: 'Token expired' }); + } + + // GitHub API错误 + if (err.response && err.response.status) { + const status = err.response.status; + const message = err.response.data?.message || 'GitHub API error'; + + if (status === 401) { + return res.status(500).json({ + error: 'GitHub authentication failed', + details: 'GitHub token may be invalid or expired', + suggestion: 'Check GitHub token configuration' + }); + } + + if (status === 403) { + return res.status(500).json({ + error: 'GitHub access forbidden', + details: 'Insufficient permissions or rate limit exceeded', + suggestion: 'Check repository permissions or wait for rate limit reset' + }); + } + + if (status === 404) { + return res.status(404).json({ + error: 'Resource not found in GitHub', + details: message, + suggestion: 'Check if the repository or file exists' + }); + } + + if (status === 422) { + return res.status(413).json({ + error: 'File too large for GitHub', + details: message, + suggestion: 'Try reducing file size or splitting into smaller files' + }); + } + + return res.status(500).json({ + error: `GitHub API error: ${message}`, + details: `HTTP ${status}: ${message}`, + suggestion: 'Check GitHub service status or try again later' + }); + } + + // Axios 网络错误 + if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') { + return res.status(503).json({ + error: 'Service unavailable', + details: 'Cannot connect to GitHub API', + suggestion: 'Check network connection or GitHub service status' + }); + } + + if (err.code === 'ETIMEDOUT') { + return res.status(504).json({ + error: 'Request timeout', + details: 'GitHub API request timed out', + suggestion: 'Try again with smaller data or check network connection' + }); + } + + // 自定义应用错误 + if (err.message.includes('Too many slides failed to save')) { + return res.status(500).json({ + error: 'Partial save failure', + details: err.message, + suggestion: 'Some slides could not be saved. Check individual slide content and try again.', + partialFailure: true + }); + } + + if (err.message.includes('Failed to save PPT')) { + return res.status(500).json({ + error: 'PPT save failed', + details: err.message, + suggestion: 'Check PPT content and try saving again. Consider reducing file size if needed.' + }); + } + + // 默认错误 + const isDevelopment = process.env.NODE_ENV === 'development'; + + res.status(500).json({ + error: isDevelopment ? err.message : 'Internal server error', + details: isDevelopment ? err.stack : 'An unexpected error occurred', + suggestion: 'If the problem persists, please contact support', + timestamp: new Date().toISOString() + }); +}; \ No newline at end of file diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..de71b83ec04a41bb83a41d20138cdf95c9d5ad34 --- /dev/null +++ b/backend/src/routes/auth.js @@ -0,0 +1,149 @@ +import express from 'express'; +import jwt from 'jsonwebtoken'; +import bcrypt from 'bcryptjs'; +import { USERS, JWT_SECRET, JWT_EXPIRES_IN, JWT_ENABLED } from '../config/users.js'; + +const router = express.Router(); + +// 登录 +router.post('/login', async (req, res, next) => { + try { + const { username, password } = req.body; + + // 如果JWT未启用,返回默认用户 + if (!JWT_ENABLED) { + console.log('JWT disabled, returning default user for login'); + return res.json({ + token: 'no-auth-required', + user: { + id: 'PS01', + username: 'PS01', + role: 'admin' + }, + message: 'Authentication disabled' + }); + } + + if (!username || !password) { + return res.status(400).json({ error: 'Username and password are required' }); + } + + // 查找用户 + const user = USERS.find(u => u.username === username); + if (!user) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + // 验证密码 + if (user.password !== password) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + // 生成JWT token + const token = jwt.sign( + { + userId: user.id, + username: user.username, + role: user.role + }, + JWT_SECRET, + { expiresIn: JWT_EXPIRES_IN } + ); + + res.json({ + token, + user: { + id: user.id, + username: user.username, + role: user.role + } + }); + } catch (error) { + next(error); + } +}); + +// 验证token +router.get('/verify', (req, res, next) => { + try { + // 如果JWT未启用,返回默认用户 + if (!JWT_ENABLED) { + console.log('JWT disabled, returning default user for verify'); + return res.json({ + user: { + id: 'PS01', + username: 'PS01', + role: 'admin' + }, + message: 'Authentication disabled' + }); + } + + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + return res.status(401).json({ error: 'No token provided' }); + } + + jwt.verify(token, JWT_SECRET, (err, decoded) => { + if (err) { + return res.status(401).json({ error: 'Invalid token' }); + } + + res.json({ + user: { + id: decoded.userId, + username: decoded.username, + role: decoded.role + } + }); + }); + } catch (error) { + next(error); + } +}); + +// 获取用户信息 +router.get('/user', (req, res, next) => { + try { + // 如果JWT未启用,返回默认用户 + if (!JWT_ENABLED) { + console.log('JWT disabled, returning default user for user info'); + return res.json({ + id: 'PS01', + username: 'PS01', + role: 'admin', + message: 'Authentication disabled' + }); + } + + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + return res.status(401).json({ error: 'No token provided' }); + } + + jwt.verify(token, JWT_SECRET, (err, decoded) => { + if (err) { + return res.status(401).json({ error: 'Invalid token' }); + } + + const user = USERS.find(u => u.id === decoded.userId); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + res.json({ + id: user.id, + username: user.username, + role: user.role + }); + }); + } catch (error) { + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/export.js b/backend/src/routes/export.js new file mode 100644 index 0000000000000000000000000000000000000000..83c36fd932a159c5689bb85a151e2a495044d014 --- /dev/null +++ b/backend/src/routes/export.js @@ -0,0 +1,631 @@ +import express from 'express'; +import persistentImageLinkService from '../services/persistentImageLinkService.js'; +import githubService from '../services/githubService.js'; +import huggingfaceStorageService from '../services/huggingfaceStorageService.js'; +import { authenticateToken } from '../middleware/auth.js'; + +const router = express.Router(); + +/** + * 接收前端生成的图片数据并创建持久化链接 + * POST /api/export/ppt-to-image + */ +router.post('/ppt-to-image', authenticateToken, async (req, res, next) => { + try { + const { pptId, slideIndex = 0, imageData, format = 'jpeg', metadata = {} } = req.body; + const userId = req.user.userId; + + console.log(`🖼️ Export PPT to image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`); + + // 验证参数 + if (!pptId) { + return res.status(400).json({ error: 'PPT ID is required' }); + } + + if (!imageData) { + return res.status(400).json({ error: 'Image data is required' }); + } + + // 验证图片数据格式 + if (!imageData.startsWith('data:image/')) { + return res.status(400).json({ error: 'Invalid image data format' }); + } + + // 获取PPT数据以验证权限 + let pptData; + try { + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + } catch (error) { + console.log('Huggingface storage failed, trying GitHub...'); + try { + pptData = await githubService.getPPTData(userId, pptId); + } catch (githubError) { + console.error('Both storage methods failed:', { huggingface: error.message, github: githubError.message }); + return res.status(404).json({ error: 'PPT not found' }); + } + } + + // 验证幻灯片索引 + if (!pptData.slides || slideIndex >= pptData.slides.length) { + return res.status(400).json({ error: 'Invalid slide index' }); + } + + // 将图片数据转换为Buffer并存储到内存 + const base64Data = imageData.split(',')[1]; + const imageBuffer = Buffer.from(base64Data, 'base64'); + + // 存储图片到内存中供公开访问 + try { + await huggingfaceStorageService.storeImage(userId, pptId, slideIndex, imageBuffer, { + format: format === 'jpeg' ? 'jpg' : format, + updateExisting: true + }); + console.log(`✅ Image stored in memory: ${userId}/${pptId}/${slideIndex}`); + } catch (storeError) { + console.warn(`⚠️ Failed to store image in memory: ${storeError.message}`); + } + + // 创建持久化链接 + const linkData = { + pptId, + slideIndex, + imageData, + format, + metadata: { + ...metadata, + exportedAt: new Date().toISOString(), + slideTitle: pptData.slides[slideIndex]?.title || `第 ${slideIndex + 1} 页`, + exportMethod: 'frontend' + } + }; + + const result = await persistentImageLinkService.createLink(userId, linkData); + + console.log(`✅ PPT exported successfully: ${result.linkId}`); + + res.json({ + success: true, + linkId: result.linkId, + imageUrl: result.imageUrl, + downloadUrl: result.downloadUrl, + metadata: result.metadata + }); + + } catch (error) { + console.error('Export PPT to image failed:', error); + next(error); + } +}); + +/** + * 接收前端批量生成的图片数据并创建持久化链接 + * POST /api/export/ppt-to-images-batch + */ +router.post('/ppt-to-images-batch', authenticateToken, async (req, res, next) => { + try { + const { pptId, imagesData, format = 'jpeg', metadata = {} } = req.body; + const userId = req.user.userId; + + console.log(`🖼️ Batch export PPT to images: userId=${userId}, pptId=${pptId}`); + + // 验证参数 + if (!pptId) { + return res.status(400).json({ error: 'PPT ID is required' }); + } + + if (!imagesData || !Array.isArray(imagesData) || imagesData.length === 0) { + return res.status(400).json({ error: 'Images data array is required' }); + } + + // 获取PPT数据以验证权限 + let pptData; + try { + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + } catch (error) { + console.log('Huggingface storage failed, trying GitHub...'); + try { + pptData = await githubService.getPPTData(userId, pptId); + } catch (githubError) { + console.error('Both storage methods failed:', { huggingface: error.message, github: githubError.message }); + return res.status(404).json({ error: 'PPT not found' }); + } + } + + if (!pptData.slides || pptData.slides.length === 0) { + return res.status(400).json({ error: 'No slides found in PPT' }); + } + + // 批量创建持久化链接 + const results = []; + const errors = []; + const exportTime = new Date().toISOString(); + + for (const imageItem of imagesData) { + try { + const { slideIndex, imageData } = imageItem; + + // 验证图片数据 + if (!imageData || !imageData.startsWith('data:image/')) { + errors.push({ slideIndex, error: 'Invalid image data format' }); + continue; + } + + // 验证幻灯片索引 + if (slideIndex >= pptData.slides.length) { + errors.push({ slideIndex, error: 'Invalid slide index' }); + continue; + } + + // 将图片数据转换为Buffer并存储到内存 + const base64Data = imageData.split(',')[1]; + const imageBuffer = Buffer.from(base64Data, 'base64'); + + // 存储图片到内存中供公开访问 + try { + await huggingfaceStorageService.storeImage(userId, pptId, slideIndex, imageBuffer, { + format: format === 'jpeg' ? 'jpg' : format, + updateExisting: true + }); + console.log(`✅ Batch image stored in memory: ${userId}/${pptId}/${slideIndex}`); + } catch (storeError) { + console.warn(`⚠️ Failed to store batch image in memory: ${storeError.message}`); + } + + const linkData = { + pptId, + slideIndex, + imageData, + format, + metadata: { + ...metadata, + exportedAt: exportTime, + slideTitle: pptData.slides[slideIndex]?.title || `第 ${slideIndex + 1} 页`, + exportMethod: 'frontend', + batchExport: true + } + }; + + const result = await persistentImageLinkService.createLink(userId, linkData); + results.push({ + slideIndex, + linkId: result.linkId, + imageUrl: result.imageUrl, + downloadUrl: result.downloadUrl, + metadata: result.metadata + }); + + } catch (error) { + console.error(`Failed to create link for slide ${imageItem.slideIndex}:`, error); + errors.push({ slideIndex: imageItem.slideIndex, error: error.message }); + } + } + + console.log(`✅ Batch export completed: ${results.length}/${imagesData.length} successful`); + + res.json({ + success: true, + totalSlides: imagesData.length, + successCount: results.length, + errorCount: errors.length, + results, + errors + }); + + } catch (error) { + console.error('Batch export PPT to images failed:', error); + next(error); + } +}); + +/** + * 接收前端生成的当前幻灯片图片数据并创建持久化链接 + * POST /api/export/current-slide + */ +router.post('/current-slide', authenticateToken, async (req, res, next) => { + try { + const { pptId, slideIndex, imageData, format = 'jpeg', metadata = {} } = req.body; + const userId = req.user.userId; + + console.log(`🖼️ Export current slide: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`); + + // 验证参数 + if (!pptId || slideIndex === undefined || !imageData) { + return res.status(400).json({ error: 'PPT ID, slide index and image data are required' }); + } + + // 验证图片数据格式 + if (!imageData.startsWith('data:image/')) { + return res.status(400).json({ error: 'Invalid image data format' }); + } + + // 获取PPT数据以验证权限 + let pptData; + try { + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + } catch (error) { + console.log('Huggingface storage failed, trying GitHub...'); + try { + pptData = await githubService.getPPTData(userId, pptId); + } catch (githubError) { + console.error('Both storage methods failed:', { huggingface: error.message, github: githubError.message }); + return res.status(404).json({ error: 'PPT not found' }); + } + } + + // 验证幻灯片索引 + if (!pptData.slides || slideIndex >= pptData.slides.length) { + return res.status(400).json({ error: 'Invalid slide index' }); + } + + // 将图片数据转换为Buffer并存储到内存 + const base64Data = imageData.split(',')[1]; + const imageBuffer = Buffer.from(base64Data, 'base64'); + + // 存储图片到内存中供公开访问 + try { + await huggingfaceStorageService.storeImage(userId, pptId, slideIndex, imageBuffer, { + format: format === 'jpeg' ? 'jpg' : format, + updateExisting: true + }); + console.log(`✅ Current slide image stored in memory: ${userId}/${pptId}/${slideIndex}`); + } catch (storeError) { + console.warn(`⚠️ Failed to store current slide image in memory: ${storeError.message}`); + } + + // 创建持久化链接 + const linkData = { + pptId, + slideIndex, + imageData, + format, + metadata: { + ...metadata, + exportedAt: new Date().toISOString(), + slideTitle: pptData.slides[slideIndex]?.title || `第 ${slideIndex + 1} 页`, + exportMethod: 'frontend', + isCurrentSlide: true + } + }; + + const result = await persistentImageLinkService.createLink(userId, linkData); + + console.log(`✅ Current slide exported successfully: ${result.linkId}`); + + res.json({ + success: true, + linkId: result.linkId, + imageUrl: result.imageUrl, + downloadUrl: result.downloadUrl, + metadata: result.metadata + }); + + } catch (error) { + console.error('Export current slide failed:', error); + next(error); + } +}); + +/** + * 获取导出历史记录 + * GET /api/export/history + */ +router.get('/history', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.id; + const { page = 1, limit = 20, pptId } = req.query; + + console.log(`📋 Get export history: userId=${userId}, page=${page}, limit=${limit}`); + + // 获取用户的持久化链接列表 + const links = await persistentImageLinkService.getUserLinks(userId, { + page: parseInt(page), + limit: parseInt(limit), + pptId + }); + + // 过滤出导出相关的链接(包含导出元数据) + const exportHistory = links.links.filter(link => + link.metadata && (link.metadata.exportedAt || link.metadata.isCurrentSlide || link.metadata.batchExport) + ); + + res.json({ + success: true, + total: exportHistory.length, + page: parseInt(page), + limit: parseInt(limit), + history: exportHistory.map(link => ({ + linkId: link.linkId, + pptId: link.pptId, + slideIndex: link.slideIndex, + imageUrl: link.imageUrl, + downloadUrl: link.downloadUrl, + createdAt: link.createdAt, + expiresAt: link.expiresAt, + metadata: link.metadata + })) + }); + + } catch (error) { + console.error('Get export history failed:', error); + next(error); + } +}); + +/** + * 删除导出记录 + * DELETE /api/export/:linkId + */ +router.delete('/:linkId', authenticateToken, async (req, res, next) => { + try { + const { linkId } = req.params; + const userId = req.user.id; + + console.log(`🗑️ Delete export record: userId=${userId}, linkId=${linkId}`); + + // 删除持久化链接 + const result = await persistentImageLinkService.deleteLink(linkId, userId); + + if (!result.success) { + return res.status(404).json({ error: 'Export record not found or access denied' }); + } + + console.log(`✅ Export record deleted: ${linkId}`); + + res.json({ + success: true, + message: 'Export record deleted successfully' + }); + + } catch (error) { + console.error('Delete export record failed:', error); + next(error); + } +}); + +/** + * 前端导出功能复刻 - 单个幻灯片导出 + * POST /api/export/frontend-replicate-single + */ +router.post('/frontend-replicate-single', authenticateToken, async (req, res, next) => { + try { + const { pptId, slideIndex = 0, format = 'jpeg', quality = 1, width = 1600, height = 900, ignoreWebfont = true, backgroundColor = '#ffffff', pixelRatio = 1 } = req.body; + const userId = req.user.userId; + + console.log(`🖼️ Frontend replicate single slide: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`); + + // 验证参数 + if (!pptId) { + return res.status(400).json({ error: 'PPT ID is required' }); + } + + // 获取PPT数据以验证权限 + let pptData; + try { + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + } catch (error) { + console.log('Huggingface storage failed, trying GitHub...'); + try { + pptData = await githubService.getPPTData(userId, pptId); + } catch (githubError) { + console.error('Both storage methods failed:', { huggingface: error.message, github: githubError.message }); + return res.status(404).json({ error: 'PPT not found' }); + } + } + + // 验证幻灯片索引 + if (!pptData.slides || slideIndex >= pptData.slides.length) { + return res.status(400).json({ error: 'Invalid slide index' }); + } + + // 返回指导信息,因为实际的图片生成需要在前端完成 + const response = { + success: false, + message: 'Frontend rendering required', + instruction: { + method: 'frontend-rendering', + description: 'Please use frontend html-to-image library to generate the image data, then call /api/export/ppt-to-image to create persistent link', + steps: [ + '1. Use html-to-image library in frontend to capture slide DOM', + '2. Convert captured image to base64 data URL', + '3. Call /api/export/ppt-to-image with the image data', + '4. Receive persistent link for the generated image' + ], + alternativeApi: '/api/export/ppt-to-image', + requiredParams: { + pptId, + slideIndex, + imageData: 'data:image/jpeg;base64,...', + format, + metadata: { + width, + height, + quality, + backgroundColor, + pixelRatio, + ignoreWebfont, + exportMethod: 'frontend-replication' + } + } + }, + slideInfo: { + title: pptData.slides[slideIndex]?.title || `第 ${slideIndex + 1} 页`, + slideCount: pptData.slides.length + } + }; + + res.json(response); + + } catch (error) { + console.error('Frontend replicate single slide failed:', error); + next(error); + } +}); + +/** + * 前端导出功能复刻 - 批量导出 + * POST /api/export/frontend-replicate-batch + */ +router.post('/frontend-replicate-batch', authenticateToken, async (req, res, next) => { + try { + const { pptId, rangeType = 'all', range, currentSlideIndex = 0, format = 'jpeg', quality = 1, width = 1600, height = 900 } = req.body; + const userId = req.user.userId; + + console.log(`🖼️ Frontend replicate batch export: userId=${userId}, pptId=${pptId}, rangeType=${rangeType}`); + + // 验证参数 + if (!pptId) { + return res.status(400).json({ error: 'PPT ID is required' }); + } + + // 获取PPT数据以验证权限 + let pptData; + try { + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + } catch (error) { + console.log('Huggingface storage failed, trying GitHub...'); + try { + pptData = await githubService.getPPTData(userId, pptId); + } catch (githubError) { + console.error('Both storage methods failed:', { huggingface: error.message, github: githubError.message }); + return res.status(404).json({ error: 'PPT not found' }); + } + } + + if (!pptData.slides || pptData.slides.length === 0) { + return res.status(400).json({ error: 'No slides found in PPT' }); + } + + // 确定要导出的幻灯片范围 + let slideIndices = []; + switch (rangeType) { + case 'all': + slideIndices = Array.from({ length: pptData.slides.length }, (_, i) => i); + break; + case 'current': + if (currentSlideIndex >= 0 && currentSlideIndex < pptData.slides.length) { + slideIndices = [currentSlideIndex]; + } + break; + case 'custom': + if (Array.isArray(range) && range.length === 2) { + const [start, end] = range; + for (let i = Math.max(0, start); i <= Math.min(end, pptData.slides.length - 1); i++) { + slideIndices.push(i); + } + } + break; + } + + if (slideIndices.length === 0) { + return res.status(400).json({ error: 'No valid slides to export' }); + } + + // 返回批量导出指导信息 + const response = { + success: false, + message: 'Frontend rendering required for batch export', + instruction: { + method: 'frontend-batch-rendering', + description: 'Please use frontend to generate all slide images, then call /api/export/ppt-to-images-batch', + slideIndices, + totalSlides: slideIndices.length, + steps: [ + '1. Loop through each slide index in slideIndices array', + '2. For each slide, use html-to-image library to capture DOM', + '3. Collect all image data in the required format', + '4. Call /api/export/ppt-to-images-batch with all image data', + '5. Receive persistent links for all generated images' + ], + alternativeApi: '/api/export/ppt-to-images-batch', + requiredFormat: { + pptId, + imagesData: [ + { + slideIndex: 'number', + imageData: 'data:image/jpeg;base64,...' + } + ], + format, + metadata: { + width, + height, + quality, + exportMethod: 'frontend-batch-replication', + rangeType, + range: rangeType === 'custom' ? range : undefined + } + } + }, + pptInfo: { + title: pptData.title || '未命名演示文稿', + slideCount: pptData.slides.length, + exportRange: { + type: rangeType, + indices: slideIndices, + total: slideIndices.length + } + } + }; + + res.json(response); + + } catch (error) { + console.error('Frontend replicate batch export failed:', error); + next(error); + } +}); + +/** + * 前端导出服务健康检查 + * GET /api/export/health + */ +router.get('/health', async (req, res, next) => { + try { + console.log('🔍 Checking frontend export service health...'); + + // 检查持久化链接服务状态 + const linkServiceStatus = await persistentImageLinkService.healthCheck(); + + // 检查存储服务状态 + const storageStatus = await huggingfaceStorageService.healthCheck(); + + const isHealthy = linkServiceStatus.status === 'healthy' && storageStatus.status === 'healthy'; + + const healthStatus = { + status: isHealthy ? 'healthy' : 'degraded', + timestamp: new Date().toISOString(), + services: { + persistentImageLink: linkServiceStatus, + storage: storageStatus + }, + exportMethod: 'frontend-based', + browserDependencies: 'removed' + }; + + if (isHealthy) { + res.json({ + success: true, + service: 'Frontend Export Service', + ...healthStatus + }); + } else { + res.status(503).json({ + success: false, + service: 'Frontend Export Service', + ...healthStatus + }); + } + + } catch (error) { + console.error('Health check failed:', error); + res.status(503).json({ + success: false, + status: 'error', + service: 'Frontend Export Service', + error: error.message, + timestamp: new Date().toISOString() + }); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/health.js b/backend/src/routes/health.js new file mode 100644 index 0000000000000000000000000000000000000000..9627d0f7525418df56ec956684eb857a373f7868 --- /dev/null +++ b/backend/src/routes/health.js @@ -0,0 +1,93 @@ +import express from 'express'; +import { performance } from 'perf_hooks'; +import os from 'os'; + +const router = express.Router(); + +// 检查数据库连接状态 +const checkDatabase = async () => { + try { + // 这里可以添加实际的数据库连接检查 + return { status: 'healthy', message: 'Database connection OK' }; + } catch (error) { + return { status: 'unhealthy', message: error.message }; + } +}; + +// 检查浏览器服务状态 +const checkBrowserServices = async () => { + try { + // 这里可以添加浏览器服务的健康检查 + return { status: 'healthy', message: 'Browser services OK' }; + } catch (error) { + return { status: 'unhealthy', message: error.message }; + } +}; + +// 检查存储服务状态 +const checkStorage = async () => { + try { + // 这里可以添加存储服务的健康检查 + return { status: 'healthy', message: 'Storage services OK' }; + } catch (error) { + return { status: 'unhealthy', message: error.message }; + } +}; + +// 健康检查端点 +router.get('/health', async (req, res) => { + const startTime = performance.now(); + + try { + const [database, browser, storage] = await Promise.all([ + checkDatabase(), + checkBrowserServices(), + checkStorage() + ]); + + const responseTime = performance.now() - startTime; + + const healthCheck = { + status: 'OK', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + responseTime: `${responseTime.toFixed(2)}ms`, + version: process.env.npm_package_version || '1.0.0', + environment: process.env.NODE_ENV || 'development', + memory: { + used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100, + total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024 * 100) / 100, + system: Math.round(os.totalmem() / 1024 / 1024 * 100) / 100 + }, + services: { + database, + browser, + storage + } + }; + + // 检查是否有任何服务不健康 + const hasUnhealthyService = Object.values(healthCheck.services) + .some(service => service.status === 'unhealthy'); + + const statusCode = hasUnhealthyService ? 503 : 200; + res.status(statusCode).json(healthCheck); + + } catch (error) { + const responseTime = performance.now() - startTime; + + res.status(503).json({ + status: 'ERROR', + timestamp: new Date().toISOString(), + responseTime: `${responseTime.toFixed(2)}ms`, + error: error.message + }); + } +}); + +// 简单的存活检查 +router.get('/ping', (req, res) => { + res.status(200).json({ message: 'pong', timestamp: new Date().toISOString() }); +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/images.js b/backend/src/routes/images.js new file mode 100644 index 0000000000000000000000000000000000000000..53c12fcecd1958d9de2399b1d7708f6198290902 --- /dev/null +++ b/backend/src/routes/images.js @@ -0,0 +1,450 @@ +import express from 'express'; +import { authenticateToken } from '../middleware/auth.js'; +import huggingfaceStorageService from '../services/huggingfaceStorageService.js'; +import backupSchedulerService from '../services/backupSchedulerService.js'; +import githubService from '../services/githubService.js'; + +const router = express.Router(); + +// 添加路由级别的日志中间件 +router.use((req, res, next) => { + console.log(`Images Router - ${req.method} ${req.path}`); + next(); +}); + +/** + * 生成单页PPT图片 + * POST /api/images/generate + */ +router.post('/generate', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { + pptId, + pageIndex, + slideData, + options = {} + } = req.body; + + // 验证必需参数 + if (!pptId || pageIndex === undefined || !slideData) { + return res.status(400).json({ + error: 'Missing required parameters: pptId, pageIndex, slideData' + }); + } + + console.log(`🖼️ Generating image for PPT ${pptId}, page ${pageIndex}`); + + // 设置默认选项 + const generateOptions = { + format: 'png', + quality: 0.9, + width: 1920, + height: 1080, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + }; + + // 图片生成服务已被移除 + return res.status(503).json({ + success: false, + error: 'Image generation service unavailable', + message: 'Browser dependencies have been removed' + }); + + // 存储图片 + const storeResult = await huggingfaceStorageService.storeImage( + userId, + pptId, + pageIndex, + imageBuffer, + { + format: generateOptions.format, + quality: generateOptions.quality + } + ); + + if (storeResult.success) { + // 调度备份 + backupSchedulerService.scheduleUserBackup(userId); + + res.json({ + success: true, + imageId: storeResult.imageId, + versionedImageId: storeResult.versionedImageId, + url: storeResult.url, + versionedUrl: storeResult.versionedUrl, + version: storeResult.version, + size: storeResult.size, + format: generateOptions.format + }); + } else { + throw new Error('Failed to store image'); + } + } catch (error) { + console.error('❌ Failed to generate image:', error); + next(error); + } +}); + +/** + * 批量生成PPT所有页面图片 + * POST /api/images/generate-all + */ +router.post('/generate-all', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { + pptId, + slides, + options = {} + } = req.body; + + // 验证必需参数 + if (!pptId || !slides || !Array.isArray(slides)) { + return res.status(400).json({ + error: 'Missing required parameters: pptId, slides (array)' + }); + } + + console.log(`🖼️ Generating ${slides.length} images for PPT ${pptId}`); + + // 设置默认选项 + const generateOptions = { + format: 'png', + quality: 0.9, + width: 1920, + height: 1080, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + }; + + // 批量生成图片 + const imageResults = await imageGenerationService.generateAllSlideImages( + slides, + generateOptions + ); + + // 批量存储图片 + const storeResults = await huggingfaceStorageService.storeAllImages( + userId, + pptId, + imageResults, + { + format: generateOptions.format, + quality: generateOptions.quality + } + ); + + // 统计结果 + const successCount = storeResults.filter(r => r.success).length; + const failureCount = storeResults.length - successCount; + + console.log(`✅ Generated ${successCount} images, ${failureCount} failed`); + + // 调度备份 + if (successCount > 0) { + backupSchedulerService.scheduleUserBackup(userId); + } + + res.json({ + success: true, + totalSlides: slides.length, + successCount, + failureCount, + results: storeResults.map(result => ({ + pageIndex: result.pageIndex, + success: result.success, + imageId: result.imageId, + url: result.url, + error: result.error + })) + }); + } catch (error) { + console.error('❌ Failed to generate all images:', error); + next(error); + } +}); + +/** + * 获取图片文件 + * GET /api/images/:imageId + */ +router.get('/:imageId', async (req, res, next) => { + try { + const { imageId } = req.params; + const { download = false } = req.query; + + console.log(`📷 Requesting image: ${imageId}`); + + // 获取图片 + const imageResult = await huggingfaceStorageService.getImage(imageId); + + if (!imageResult.success) { + return res.status(404).json({ + error: 'Image not found', + imageId + }); + } + + const { data: imageBuffer, metadata } = imageResult; + + // 设置响应头 + const mimeType = metadata.format === 'png' ? 'image/png' : 'image/jpeg'; + res.set({ + 'Content-Type': mimeType, + 'Content-Length': imageBuffer.length, + 'Cache-Control': 'public, max-age=31536000', // 1年缓存 + 'ETag': `"${imageId}"`, + 'Last-Modified': new Date(metadata.updatedAt).toUTCString() + }); + + // 如果是下载请求,设置下载头 + if (download) { + const fileName = `slide-${metadata.imageId}.${metadata.format}`; + res.set('Content-Disposition', `attachment; filename="${fileName}"`); + } + + // 检查条件请求 + const ifNoneMatch = req.get('If-None-Match'); + if (ifNoneMatch === `"${imageId}"`) { + return res.status(304).end(); + } + + res.send(imageBuffer); + } catch (error) { + console.error(`❌ Failed to get image ${req.params.imageId}:`, error); + next(error); + } +}); + +/** + * 删除图片 + * DELETE /api/images/:imageId + */ +router.delete('/:imageId', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { imageId } = req.params; + + console.log(`🗑️ Deleting image: ${imageId}`); + + // 验证图片所有权(通过获取图片信息) + try { + const imageResult = await huggingfaceStorageService.getImage(imageId); + if (!imageResult.success) { + return res.status(404).json({ + error: 'Image not found', + imageId + }); + } + } catch (error) { + return res.status(404).json({ + error: 'Image not found', + imageId + }); + } + + // 删除图片 + const deleteResult = await huggingfaceStorageService.deleteImage(imageId); + + if (deleteResult) { + // 调度备份 + backupSchedulerService.scheduleUserBackup(userId); + + res.json({ + success: true, + message: 'Image deleted successfully', + imageId + }); + } else { + res.status(500).json({ + error: 'Failed to delete image', + imageId + }); + } + } catch (error) { + console.error(`❌ Failed to delete image ${req.params.imageId}:`, error); + next(error); + } +}); + +/** + * 获取用户的所有图片 + * GET /api/images/user/list + */ +router.get('/user/list', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.query; + + console.log(`📋 Getting images for user ${userId}${pptId ? `, PPT ${pptId}` : ''}`); + + const userImages = await huggingfaceStorageService.getUserImages(userId, pptId); + + res.json({ + success: true, + userId, + pptId: pptId || null, + imageCount: userImages.length, + images: userImages + }); + } catch (error) { + console.error(`❌ Failed to get user images:`, error); + next(error); + } +}); + +/** + * 重新生成PPT页面图片 + * PUT /api/images/regenerate + */ +router.put('/regenerate', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { + pptId, + pageIndex, + slideData, + options = {} + } = req.body; + + // 验证必需参数 + if (!pptId || pageIndex === undefined || !slideData) { + return res.status(400).json({ + error: 'Missing required parameters: pptId, pageIndex, slideData' + }); + } + + console.log(`🔄 Regenerating image for PPT ${pptId}, page ${pageIndex}`); + + // 设置默认选项 + const generateOptions = { + format: 'png', + quality: 0.9, + width: 1920, + height: 1080, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + }; + + // 生成新图片 + const imageBuffer = await imageGenerationService.generateSlideImage( + slideData, + generateOptions + ); + + // 存储图片(会自动覆盖旧图片,但保持相同的链接) + const storeResult = await huggingfaceStorageService.storeImage( + userId, + pptId, + pageIndex, + imageBuffer, + { + format: generateOptions.format, + quality: generateOptions.quality, + updateExisting: true + } + ); + + if (storeResult.success) { + // 调度备份 + backupSchedulerService.scheduleUserBackup(userId); + + res.json({ + success: true, + message: 'Image regenerated successfully', + imageId: storeResult.imageId, + url: storeResult.url, + version: storeResult.version, + size: storeResult.size + }); + } else { + throw new Error('Failed to store regenerated image'); + } + } catch (error) { + console.error('❌ Failed to regenerate image:', error); + next(error); + } +}); + +/** + * 获取存储统计信息 + * GET /api/images/stats + */ +router.get('/stats', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + + console.log(`📊 Getting storage stats for user ${userId}`); + + const storageStats = await huggingfaceStorageService.getStorageStats(); + const backupStatus = backupSchedulerService.getBackupStatus(); + + // 过滤用户特定的统计信息 + const userStats = storageStats.userStats[userId] || { + imageCount: 0, + totalSize: 0, + pptCount: 0 + }; + + res.json({ + success: true, + userId, + userStats: { + ...userStats, + totalSizeMB: Math.round(userStats.totalSize / 1024 / 1024 * 100) / 100 + }, + systemStats: { + totalImages: storageStats.totalImages, + totalSizeMB: storageStats.totalSizeMB, + userCount: storageStats.userCount + }, + backupStatus: { + initialized: backupStatus.initialized, + scheduledBackups: backupStatus.scheduledBackups, + backupsInProgress: backupStatus.backupsInProgress + } + }); + } catch (error) { + console.error('❌ Failed to get storage stats:', error); + next(error); + } +}); + +/** + * 手动触发备份 + * POST /api/images/backup + */ +router.post('/backup', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + + console.log(`💾 Manual backup triggered for user ${userId}`); + + // 立即执行备份 + const backupResult = await backupSchedulerService.backupUserData(userId); + + if (backupResult) { + res.json({ + success: true, + message: 'Backup completed successfully', + userId, + timestamp: new Date().toISOString() + }); + } else { + res.status(500).json({ + error: 'Backup failed', + userId + }); + } + } catch (error) { + console.error('❌ Manual backup failed:', error); + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/persistentImages.js b/backend/src/routes/persistentImages.js new file mode 100644 index 0000000000000000000000000000000000000000..8736fbd1610272f2368a1b04aaec1e912c1c804b --- /dev/null +++ b/backend/src/routes/persistentImages.js @@ -0,0 +1,540 @@ +import express from 'express'; +import { authenticateToken } from '../middleware/auth.js'; +import persistentImageLinkService from '../services/persistentImageLinkService.js'; +import githubService from '../services/githubService.js'; + +const router = express.Router(); + +// 添加路由级别的日志中间件 +router.use((req, res, next) => { + console.log(`Persistent Images Router - ${req.method} ${req.path}`); + next(); +}); + +/** + * 获取或创建PPT页面的持久化链接 + * POST /api/persistent-images/create + */ +router.post('/create', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { + pptId, + pageIndex, + slideId, + slideData, + options = {} + } = req.body; + + // 验证必需参数 - 需要slideId或pageIndex其中之一 + if (!pptId || (slideId === undefined && pageIndex === undefined)) { + return res.status(400).json({ + error: 'Missing required parameters: pptId and (slideId or pageIndex)' + }); + } + + // 优先使用slideId,如果不存在则使用pageIndex + const uniqueId = slideId || pageIndex; + + console.log(`🔗 Creating/getting persistent link for PPT ${pptId}, uniqueId ${uniqueId}`); + + // 获取或创建持久化链接 + const result = await persistentImageLinkService.getOrCreatePersistentLink( + userId, + pptId, + uniqueId, + slideData, + options + ); + + res.json({ + success: true, + message: 'Persistent link created/retrieved successfully', + ...result + }); + } catch (error) { + console.error('❌ Failed to create persistent link:', error); + next(error); + } +}); + +/** + * 批量创建PPT所有页面的持久化链接 + * POST /api/persistent-images/create-all + */ +router.post('/create-all', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { + pptId, + slides, + options = {} + } = req.body; + + // 验证必需参数 + if (!pptId || !slides || !Array.isArray(slides)) { + return res.status(400).json({ + error: 'Missing required parameters: pptId, slides (array)' + }); + } + + console.log(`🔗 Creating persistent links for ${slides.length} pages of PPT ${pptId}`); + + // 批量创建持久化链接 + const results = await persistentImageLinkService.updateAllPersistentLinks( + userId, + pptId, + slides, + options + ); + + // 统计结果 + const successCount = results.filter(r => r.success).length; + const failureCount = results.length - successCount; + + console.log(`✅ Created ${successCount} persistent links, ${failureCount} failed`); + + // 转换结果格式以匹配前端期望的数据结构 + const links = results.filter(r => r.success).map(result => ({ + linkId: result.linkId, + url: result.url, + publicUrl: result.publicUrl, + pageIndex: result.pageIndex, + slideId: result.slideId, + lastUpdated: result.lastUpdated + })); + + res.json({ + success: true, + message: `Created ${successCount} persistent links successfully`, + totalPages: slides.length, + successCount, + failureCount, + links, + results + }); + } catch (error) { + console.error('❌ Failed to create persistent links:', error); + next(error); + } +}); + +/** + * 更新PPT页面图片 + * PUT /api/persistent-images/:linkId + */ +router.put('/:linkId', authenticateToken, async (req, res, next) => { + try { + const { linkId } = req.params; + const { + slideData, + options = {} + } = req.body; + + // 验证必需参数 + if (!slideData) { + return res.status(400).json({ + error: 'Missing required parameter: slideData' + }); + } + + console.log(`🔄 Updating image for persistent link: ${linkId}`); + + // 更新图片 + await persistentImageLinkService.updatePageImage(linkId, slideData, options); + + res.json({ + success: true, + message: 'Image updated successfully', + linkId, + url: `/api/persistent-images/${linkId}` + }); + } catch (error) { + console.error(`❌ Failed to update persistent link ${req.params.linkId}:`, error); + next(error); + } +}); + +/** + * 更新持久化链接的图片内容(通过图片数据) + * POST /api/persistent-images/update-image + */ +router.post('/update-image', authenticateToken, async (req, res, next) => { + try { + const { + linkId, + imageData, + format = 'jpeg' + } = req.body; + + // 验证必需参数 + if (!linkId || !imageData) { + return res.status(400).json({ + error: 'Missing required parameters: linkId, imageData' + }); + } + + console.log(`🔄 Updating image content for persistent link: ${linkId}`); + + // 将base64图片数据转换为Buffer + let imageBuffer; + try { + // 移除data:image/jpeg;base64,前缀(如果存在) + const base64Data = imageData.replace(/^data:image\/[a-z]+;base64,/, ''); + imageBuffer = Buffer.from(base64Data, 'base64'); + } catch (error) { + return res.status(400).json({ + error: 'Invalid image data format' + }); + } + + // 直接更新持久化链接的图片内容 + const result = await persistentImageLinkService.updateImageContent(linkId, imageBuffer, format); + + if (!result.success) { + return res.status(404).json({ + error: 'Persistent link not found', + linkId + }); + } + + res.json({ + success: true, + message: 'Image content updated successfully', + linkId, + url: `/api/persistent-images/${linkId}`, + size: imageBuffer.length, + format + }); + } catch (error) { + console.error(`❌ Failed to update image content for link ${req.body.linkId}:`, error); + next(error); + } +}); + +/** + * 获取用户的所有持久化链接 + * GET /api/persistent-images/user/list + */ +router.get('/user/list', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.query; + + console.log(`📋 Getting persistent links for user ${userId}${pptId ? `, PPT ${pptId}` : ''}`); + + const userLinks = persistentImageLinkService.getUserPersistentLinks(userId, pptId); + + res.json({ + success: true, + userId, + pptId: pptId || null, + linkCount: userLinks.length, + links: userLinks + }); + } catch (error) { + console.error(`❌ Failed to get user persistent links:`, error); + next(error); + } +}); + +/** + * 获取服务统计信息 + * GET /api/persistent-images/stats + */ +router.get('/stats', authenticateToken, async (req, res, next) => { + try { + console.log('📊 Getting persistent image service stats'); + + const stats = persistentImageLinkService.getStats(); + + res.json({ + success: true, + stats + }); + } catch (error) { + console.error('❌ Failed to get persistent image stats:', error); + next(error); + } +}); + +/** + * 获取持久化链接的图片 (通过 pptId 和 slideId) + * GET /api/persistent-images/:pptId/:slideId + */ +router.get('/:pptId/:slideId', async (req, res, next) => { + try { + const { pptId, slideId } = req.params; + const { download } = req.query; + + // 验证pptId格式 (应该是UUID格式) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(pptId)) { + return next(); // 不是UUID格式,继续到下一个路由 + } + + // 验证slideId格式 (不应该包含文件扩展名) + if (slideId.includes('.')) { + return next(); // 包含文件扩展名,可能是静态文件请求 + } + + console.log(`🖼️ Getting persistent image by pptId: ${pptId}, slideId: ${slideId}`); + + // 通过 pptId 和 slideId 查找对应的链接 + const linkInfo = await persistentImageLinkService.findLinkByPptAndSlide(pptId, slideId); + + if (!linkInfo) { + return res.status(404).json({ + error: 'Persistent image not found for the specified PPT and slide', + pptId, + slideId + }); + } + + // 获取图片 + const imageResult = await persistentImageLinkService.getPersistentImage(linkInfo.linkId); + + if (!imageResult.success) { + return res.status(404).json({ + error: 'Persistent image not found', + pptId, + slideId, + linkId: linkInfo.linkId + }); + } + + const { data: imageBuffer, metadata } = imageResult; + + // 设置响应头 + const contentType = metadata.format === 'jpg' ? 'image/jpeg' : `image/${metadata.format}`; + res.set({ + 'Content-Type': contentType, + 'Content-Length': metadata.size, + 'Cache-Control': 'public, max-age=3600', // 缓存1小时 + 'ETag': `"${linkInfo.linkId}"`, + 'Last-Modified': new Date(metadata.lastUpdated).toUTCString() + }); + + // 如果是下载请求,设置下载头 + if (download) { + const fileName = `persistent-slide-${pptId}-${slideId}.${metadata.format}`; + res.set('Content-Disposition', `attachment; filename="${fileName}"`); + } + + // 检查条件请求 + const ifNoneMatch = req.get('If-None-Match'); + if (ifNoneMatch === `"${linkInfo.linkId}"`) { + return res.status(304).end(); + } + + res.send(imageBuffer); + } catch (error) { + console.error(`❌ Failed to get persistent image ${req.params.pptId}/${req.params.slideId}:`, error); + if (error.message.includes('not found')) { + return res.status(404).json({ + error: 'Persistent image not found', + pptId: req.params.pptId, + slideId: req.params.slideId + }); + } + next(error); + } +}); + +/** + * 获取持久化链接的图片 (通过 pptId 或 linkId) + * GET /api/persistent-images/:id + */ +router.get('/:id', async (req, res, next) => { + try { + const { id } = req.params; + const { download } = req.query; + + console.log(`🖼️ Getting persistent image: ${id}`); + + // 检查是否是PPT ID格式(通常比linkId短,且可能包含特殊字符) + // 如果是PPT ID,返回该PPT的第一页图片 + let imageResult; + let isLinkId = false; + + try { + // 首先尝试作为linkId获取图片 + imageResult = await persistentImageLinkService.getPersistentImage(id); + isLinkId = true; + } catch (error) { + // 如果作为linkId失败,尝试作为PPT ID获取第一页图片 + console.log(`⚠️ Not found as linkId, trying as PPT ID: ${id}`); + + // 查找该PPT的第一页链接 + const links = []; + for (const [linkId, linkInfo] of persistentImageLinkService.persistentLinks) { + if (linkInfo.pptId === id && linkInfo.hasImage) { + links.push({ linkId, pageIndex: linkInfo.pageIndex || 0 }); + } + } + + if (links.length === 0) { + return res.status(404).json({ + error: 'No persistent images found for PPT', + pptId: id + }); + } + + // 按页面索引排序,取第一页 + links.sort((a, b) => a.pageIndex - b.pageIndex); + const firstPageLinkId = links[0].linkId; + + imageResult = await persistentImageLinkService.getPersistentImage(firstPageLinkId); + } + + if (!imageResult.success) { + return res.status(404).json({ + error: 'Persistent image not found', + id + }); + } + + const { data: imageBuffer, metadata } = imageResult; + + // 设置响应头 + const contentType = metadata.format === 'jpg' ? 'image/jpeg' : `image/${metadata.format}`; + res.set({ + 'Content-Type': contentType, + 'Content-Length': metadata.size, + 'Cache-Control': 'public, max-age=3600', // 缓存1小时 + 'ETag': `"${id}"`, + 'Last-Modified': new Date(metadata.lastUpdated).toUTCString() + }); + + // 如果是下载请求,设置下载头 + if (download) { + const fileName = isLinkId ? `persistent-slide-${id}.${metadata.format}` : `persistent-ppt-${id}.${metadata.format}`; + res.set('Content-Disposition', `attachment; filename="${fileName}"`); + } + + // 检查条件请求 + const ifNoneMatch = req.get('If-None-Match'); + if (ifNoneMatch === `"${id}"`) { + return res.status(304).end(); + } + + res.send(imageBuffer); + } catch (error) { + console.error(`❌ Failed to get persistent image ${req.params.id}:`, error); + if (error.message.includes('not found')) { + return res.status(404).json({ + error: 'Persistent image not found', + id: req.params.id + }); + } + next(error); + } +}); + +/** + * 删除持久化链接 + * DELETE /api/persistent-images/:linkId + */ +router.delete('/:linkId', authenticateToken, async (req, res, next) => { + try { + const { linkId } = req.params; + + console.log(`🗑️ Deleting persistent link: ${linkId}`); + + // 删除持久化链接 + const deleteResult = await persistentImageLinkService.deletePersistentLink(linkId); + + if (deleteResult) { + res.json({ + success: true, + message: 'Persistent link deleted successfully', + linkId + }); + } else { + res.status(404).json({ + error: 'Persistent link not found', + linkId + }); + } + } catch (error) { + console.error(`❌ Failed to delete persistent link ${req.params.linkId}:`, error); + next(error); + } +}); + +/** + * 从PPT数据同步创建持久化链接 + * POST /api/persistent-images/sync-from-ppt + */ +router.post('/sync-from-ppt', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.body; + + // 验证必需参数 + if (!pptId) { + return res.status(400).json({ + error: 'Missing required parameter: pptId' + }); + } + + console.log(`🔄 Syncing persistent links from PPT data: ${pptId}`); + + // 从GitHub服务获取PPT数据 + const pptData = await githubService.getPPT(userId, pptId); + + if (!pptData || !pptData.slides) { + return res.status(404).json({ + error: 'PPT not found or has no slides', + pptId + }); + } + + // 创建持久化链接(不生成图片,只创建链接) + const results = []; + for (let pageIndex = 0; pageIndex < pptData.slides.length; pageIndex++) { + try { + const result = await persistentImageLinkService.getOrCreatePersistentLink( + userId, + pptId, + pageIndex, + null, // 不提供slideData,只创建链接 + {} + ); + + results.push({ + pageIndex, + success: true, + ...result + }); + } catch (error) { + console.error(`❌ Failed to sync persistent link for page ${pageIndex}:`, error); + results.push({ + pageIndex, + success: false, + error: error.message + }); + } + } + + const successCount = results.filter(r => r.success).length; + const failureCount = results.length - successCount; + + console.log(`✅ Synced ${successCount} persistent links, ${failureCount} failed`); + + res.json({ + success: true, + message: `Synced ${successCount} persistent links from PPT data`, + pptId, + totalPages: pptData.slides.length, + successCount, + failureCount, + results + }); + } catch (error) { + console.error('❌ Failed to sync persistent links from PPT:', error); + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/persistentLinks.js b/backend/src/routes/persistentLinks.js new file mode 100644 index 0000000000000000000000000000000000000000..0243ec81ec50eabb7d80c0708b96a6c1c03f3e3e --- /dev/null +++ b/backend/src/routes/persistentLinks.js @@ -0,0 +1,240 @@ +import express from 'express'; +import persistentImageLinkService from '../services/persistentImageLinkService.js'; +import { authenticateToken } from '../middleware/auth.js'; + +const router = express.Router(); + +/** + * 生成PPT所有页面的持久化链接 + * POST /api/persistent-links/generate-all + */ +router.post('/generate-all', authenticateToken, async (req, res, next) => { + try { + const { pptId, slides, options = {} } = req.body; + const userId = req.user.userId; + + console.log(`🔗 Generate all persistent links: userId=${userId}, pptId=${pptId}, slides=${slides?.length}`); + + // 验证参数 + if (!pptId) { + return res.status(400).json({ error: 'PPT ID is required' }); + } + + if (!slides || !Array.isArray(slides) || slides.length === 0) { + return res.status(400).json({ error: 'Slides data is required' }); + } + + // 使用持久化链接服务批量生成链接 + const results = await persistentImageLinkService.updateAllPersistentLinksWithFrontendExport( + userId, + pptId, + slides, + { + format: 'jpg', + quality: 0.9, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + } + ); + + // 统计成功和失败的数量 + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + + console.log(`✅ Persistent links generation completed: ${successCount} success, ${failureCount} failed`); + + // 返回结果 + res.json({ + success: true, + message: `成功生成 ${successCount} 个持久化链接${failureCount > 0 ? `,${failureCount} 个失败` : ''}`, + links: results.filter(r => r.success).map(r => ({ + linkId: r.linkId, + url: r.url, + publicUrl: r.publicUrl, + pageIndex: r.pageIndex + })), + stats: { + total: slides.length, + success: successCount, + failure: failureCount + }, + errors: results.filter(r => !r.success).map(r => ({ + pageIndex: r.pageIndex, + error: r.error + })) + }); + + } catch (error) { + console.error('Generate all persistent links failed:', error); + next(error); + } +}); + +/** + * 获取PPT的所有持久化链接 + * GET /api/persistent-links/:pptId + */ +router.get('/:pptId', authenticateToken, async (req, res, next) => { + try { + const { pptId } = req.params; + const userId = req.user.userId; + + console.log(`🔍 Get persistent links: userId=${userId}, pptId=${pptId}`); + + // 获取用户的所有持久化链接 + const userLinks = await persistentImageLinkService.getUserLinks(userId); + + // 过滤出指定PPT的链接 + const pptLinks = userLinks.filter(link => link.pptId === pptId); + + // 按页面索引排序 + pptLinks.sort((a, b) => a.pageIndex - b.pageIndex); + + res.json({ + success: true, + links: pptLinks.map(link => ({ + linkId: link.linkId, + url: link.url, + publicUrl: link.publicUrl, + pageIndex: link.pageIndex, + lastUpdated: link.lastUpdated, + hasImage: link.hasImage, + imageSize: link.imageSize, + format: link.format + })) + }); + + } catch (error) { + console.error('Get persistent links failed:', error); + next(error); + } +}); + +/** + * 更新单个页面的持久化链接 + * POST /api/persistent-links/:pptId/:pageIndex/update + */ +router.post('/:pptId/:pageIndex/update', authenticateToken, async (req, res, next) => { + try { + const { pptId, pageIndex } = req.params; + const { slideData, options = {} } = req.body; + const userId = req.user.userId; + + console.log(`🔄 Update persistent link: userId=${userId}, pptId=${pptId}, pageIndex=${pageIndex}`); + + // 验证参数 + if (!slideData) { + return res.status(400).json({ error: 'Slide data is required' }); + } + + const pageIdx = parseInt(pageIndex); + if (isNaN(pageIdx) || pageIdx < 0) { + return res.status(400).json({ error: 'Invalid page index' }); + } + + // 获取或创建持久化链接 + const result = await persistentImageLinkService.getOrCreatePersistentLink( + userId, + pptId, + pageIdx, + slideData, + { + format: 'jpg', + quality: 0.9, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + } + ); + + console.log(`✅ Persistent link updated: ${result.linkId}`); + + res.json({ + success: true, + linkId: result.linkId, + url: result.url, + publicUrl: result.publicUrl, + pageIndex: pageIdx + }); + + } catch (error) { + console.error('Update persistent link failed:', error); + next(error); + } +}); + +/** + * 删除持久化链接 + * DELETE /api/persistent-links/:linkId + */ +router.delete('/:linkId', authenticateToken, async (req, res, next) => { + try { + const { linkId } = req.params; + const userId = req.user.userId; + + console.log(`🗑️ Delete persistent link: userId=${userId}, linkId=${linkId}`); + + // 验证链接所有权 + const linkInfo = await persistentImageLinkService.getPersistentImage(linkId); + if (!linkInfo || linkInfo.userId !== userId) { + return res.status(404).json({ error: 'Persistent link not found or access denied' }); + } + + // 删除链接 + await persistentImageLinkService.deletePersistentLink(linkId); + + console.log(`✅ Persistent link deleted: ${linkId}`); + + res.json({ + success: true, + message: 'Persistent link deleted successfully' + }); + + } catch (error) { + console.error('Delete persistent link failed:', error); + next(error); + } +}); + +/** + * 获取持久化链接服务统计信息 + * GET /api/persistent-links/stats + */ +router.get('/stats', authenticateToken, async (req, res, next) => { + try { + const userId = req.user.userId; + + console.log(`📊 Get persistent links stats: userId=${userId}`); + + // 获取用户的所有链接 + const userLinks = await persistentImageLinkService.getUserLinks(userId); + + // 计算统计信息 + const stats = { + totalLinks: userLinks.length, + linksWithImages: userLinks.filter(link => link.hasImage).length, + totalImageSize: userLinks.reduce((sum, link) => sum + (link.imageSize || 0), 0), + lastUpdated: userLinks.length > 0 ? + Math.max(...userLinks.map(link => new Date(link.lastUpdated || 0).getTime())) : null, + pptCount: new Set(userLinks.map(link => link.pptId)).size + }; + + res.json({ + success: true, + stats: { + ...stats, + lastUpdated: stats.lastUpdated ? new Date(stats.lastUpdated).toISOString() : null, + averageImageSize: stats.linksWithImages > 0 ? + Math.round(stats.totalImageSize / stats.linksWithImages) : 0, + totalImageSizeMB: Math.round(stats.totalImageSize / 1024 / 1024 * 100) / 100 + } + }); + + } catch (error) { + console.error('Get persistent links stats failed:', error); + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/ppt.js b/backend/src/routes/ppt.js new file mode 100644 index 0000000000000000000000000000000000000000..98058848b875e1cd258268bb62d7ddcdf300dc62 --- /dev/null +++ b/backend/src/routes/ppt.js @@ -0,0 +1,757 @@ +import express from 'express'; +import { v4 as uuidv4 } from 'uuid'; +import githubService from '../services/githubService.js'; + +const router = express.Router(); + +// 添加路由级别的日志中间件 +router.use((req, res, next) => { + console.log(`PPT Router - ${req.method} ${req.path} - Body:`, Object.keys(req.body || {})); + next(); +}); + +// 获取用户的PPT列表 - 优先从Huggingface硬盘读取 +router.get('/list', async (req, res, next) => { + try { + const userId = req.user.userId; + + // 优先从Huggingface存储服务获取PPT列表 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + let pptList = []; + + try { + // 尝试从Huggingface硬盘读取PPT列表 + pptList = await huggingfaceStorageService.getUserPPTList(userId); + console.log(`📁 Found ${pptList.length} PPTs from Huggingface storage for user ${userId}`); + } catch (hfError) { + console.warn('⚠️ Huggingface storage unavailable, falling back to GitHub:', hfError.message); + // 回退到GitHub存储 + pptList = await githubService.getUserPPTList(userId); + } + + res.json(pptList); + } catch (error) { + next(error); + } +}); + +// 获取指定PPT数据 - 优先从Huggingface硬盘读取 +router.get('/:pptId', async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.params; + + console.log(`🔍 Fetching PPT: ${pptId} for user: ${userId}`); + + let pptData = null; + let foundInRepo = -1; + + // 优先从Huggingface存储服务获取PPT数据 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + try { + // 尝试从Huggingface硬盘读取PPT数据 + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + foundInRepo = 0; + console.log(`✅ PPT found in Huggingface storage`); + } catch (hfError) { + console.warn('⚠️ PPT not found in Huggingface storage, trying other sources:', hfError.message); + + if (githubService.useMemoryStorage) { + // 内存存储模式 + const result = await githubService.getPPTFromMemory(userId, pptId); + if (result && result.content) { + pptData = result.content; + foundInRepo = 0; + console.log(`✅ PPT found in memory storage`); + } + } else { + // GitHub存储模式 - 尝试所有仓库 + console.log(`Available repositories: ${githubService.repositories.length}`); + + for (let i = 0; i < githubService.repositories.length; i++) { + try { + console.log(`📂 Checking repository ${i}: ${githubService.repositories[i]}`); + const result = await githubService.getPPT(userId, pptId, i); + if (result && result.content) { + pptData = result.content; + foundInRepo = i; + console.log(`✅ PPT found in repository ${i}${result.isReassembled ? ' (reassembled from chunks)' : ''}`); + break; + } + } catch (error) { + console.log(`❌ PPT not found in repository ${i}: ${error.message}`); + continue; + } + } + } + } + + if (!pptData) { + console.log(`❌ PPT ${pptId} not found for user ${userId}`); + return res.status(404).json({ + error: 'PPT not found', + pptId: pptId, + userId: userId, + storageMode: githubService.useMemoryStorage ? 'memory' : 'github' + }); + } + + // 🔧 修复:标准化PPT数据格式,确保前端兼容性 + const standardizedPptData = { + // 确保基本字段存在 + id: pptData.id || pptData.pptId || pptId, + pptId: pptData.pptId || pptData.id || pptId, + title: pptData.title || '未命名演示文稿', + + // 标准化slides数组 + slides: Array.isArray(pptData.slides) ? pptData.slides.map((slide, index) => ({ + id: slide.id || `slide-${index}`, + elements: Array.isArray(slide.elements) ? slide.elements : [], + background: slide.background || { type: 'solid', color: '#ffffff' }, + ...slide + })) : [], + + // 标准化主题 + theme: pptData.theme || { + backgroundColor: '#ffffff', + themeColor: '#d14424', + fontColor: '#333333', + fontName: 'Microsoft YaHei' + }, + + // 🔧 关键修复:确保视口信息正确传递 + viewportSize: pptData.viewportSize || 1000, + viewportRatio: pptData.viewportRatio || 0.5625, + + // 时间戳 + createdAt: pptData.createdAt || new Date().toISOString(), + updatedAt: pptData.updatedAt || new Date().toISOString(), + + // 保留其他可能的属性 + ...pptData + }; + + // 🔧 新增:数据验证和修复 + if (standardizedPptData.slides.length === 0) { + console.log(`⚠️ PPT ${pptId} has no slides, creating default slide`); + standardizedPptData.slides = [{ + id: 'default-slide', + elements: [], + background: { type: 'solid', color: '#ffffff' } + }]; + } + + // 验证视口比例的合理性 + if (standardizedPptData.viewportRatio <= 0 || standardizedPptData.viewportRatio > 2) { + console.log(`⚠️ Invalid viewportRatio ${standardizedPptData.viewportRatio}, resetting to 0.5625`); + standardizedPptData.viewportRatio = 0.5625; + } + + // 验证视口尺寸的合理性 + if (standardizedPptData.viewportSize <= 0 || standardizedPptData.viewportSize > 2000) { + console.log(`⚠️ Invalid viewportSize ${standardizedPptData.viewportSize}, resetting to 1000`); + standardizedPptData.viewportSize = 1000; + } + + console.log(`✅ Successfully found and standardized PPT ${pptId}:`, { + slidesCount: standardizedPptData.slides.length, + viewportSize: standardizedPptData.viewportSize, + viewportRatio: standardizedPptData.viewportRatio, + storageMode: githubService.useMemoryStorage ? 'memory' : `repository ${foundInRepo}` + }); + + res.json(standardizedPptData); + } catch (error) { + console.error(`❌ Error fetching PPT ${req.params.pptId}:`, error); + next(error); + } +}); + +// 保存PPT数据 - 新架构版本 +router.post('/save', async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId, title, slides, theme, viewportSize, viewportRatio } = req.body; + + console.log(`🔄 Starting PPT save for user ${userId}, PPT ID: ${pptId}`); + console.log(`📊 Request body analysis:`, { + pptId: !!pptId, + title: title?.length || 0, + slidesCount: Array.isArray(slides) ? slides.length : 'not array', + theme: !!theme, + requestSize: req.get('content-length'), + storageMode: githubService.useMemoryStorage ? 'memory' : 'github' + }); + + if (!pptId || !slides) { + console.log(`❌ Missing required fields - pptId: ${!!pptId}, slides: ${!!slides}`); + return res.status(400).json({ error: 'PPT ID and slides are required' }); + } + + // 验证slides数组 + if (!Array.isArray(slides) || slides.length === 0) { + console.log(`❌ Invalid slides array - isArray: ${Array.isArray(slides)}, length: ${slides?.length || 0}`); + return res.status(400).json({ error: 'Slides must be a non-empty array' }); + } + + // 构建标准化的PPT数据结构 + const pptData = { + id: pptId, + title: title || '未命名演示文稿', + slides: slides.map((slide, index) => ({ + id: slide.id || `slide-${index}`, + elements: Array.isArray(slide.elements) ? slide.elements : [], + background: slide.background || { type: 'solid', color: '#ffffff' }, + ...slide + })), + theme: theme || { + backgroundColor: '#ffffff', + themeColor: '#d14424', + fontColor: '#333333', + fontName: 'Microsoft YaHei' + }, + viewportSize: viewportSize || 1000, + viewportRatio: viewportRatio || 0.5625, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + // 计算文件大小分析 + const jsonData = JSON.stringify(pptData); + const totalDataSize = Buffer.byteLength(jsonData, 'utf8'); + + console.log(`📊 PPT data analysis:`); + console.log(` - Total slides: ${slides.length}`); + console.log(` - Total data size: ${totalDataSize} bytes (${(totalDataSize / 1024).toFixed(2)} KB)`); + console.log(` - Average slide size: ${(totalDataSize / slides.length / 1024).toFixed(2)} KB`); + + // 分析每个slide的大小 + const slideAnalysis = slides.map((slide, index) => { + const slideJson = JSON.stringify(slide); + const slideSize = Buffer.byteLength(slideJson, 'utf8'); + return { + index, + size: slideSize, + sizeKB: (slideSize / 1024).toFixed(2), + elementsCount: slide.elements?.length || 0, + hasLargeContent: slideSize > 800 * 1024 // 800KB+ + }; + }); + + const largeSlides = slideAnalysis.filter(s => s.hasLargeContent); + const maxSlideSize = Math.max(...slideAnalysis.map(s => s.size)); + + console.log(`📋 Slide size analysis:`); + console.log(` - Largest slide: ${(maxSlideSize / 1024).toFixed(2)} KB`); + console.log(` - Large slides (>800KB): ${largeSlides.length}`); + if (largeSlides.length > 0) { + console.log(` - Large slide indices: ${largeSlides.map(s => s.index).join(', ')}`); + } + + try { + let result; + + // 优先保存到Huggingface存储服务 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + try { + // 尝试保存到Huggingface硬盘 + result = await huggingfaceStorageService.storePPTData(userId, pptId, pptData); + console.log(`✅ PPT saved to Huggingface storage:`, result); + } catch (hfError) { + console.warn('⚠️ Failed to save to Huggingface storage, falling back to other storage:', hfError.message); + + console.log(`💾 Using fallback storage mode: ${githubService.useMemoryStorage ? 'memory' : 'GitHub folder architecture'}`); + + if (githubService.useMemoryStorage) { + // 内存存储模式 + result = await githubService.savePPTToMemory(userId, pptId, pptData); + console.log(`✅ PPT saved to memory storage:`, result); + } else { + // GitHub存储模式 - 使用新的文件夹架构 + console.log(`🐙 Using GitHub folder storage for ${pptId}`); + console.log(`📂 Available repositories: ${githubService.repositories?.length || 0}`); + + if (!githubService.repositories || githubService.repositories.length === 0) { + throw new Error('No GitHub repositories configured'); + } + + console.log(`🔍 Pre-save validation:`); + console.log(` - Repository 0: ${githubService.repositories[0]}`); + console.log(` - Token configured: ${!!githubService.token}`); + console.log(` - API URL: ${githubService.apiUrl}`); + + result = await githubService.savePPT(userId, pptId, pptData, 0); + console.log(`✅ PPT saved to GitHub folder storage:`, result); + } + } + + console.log(`✅ PPT save completed successfully:`, { + pptId, + userId, + slideCount: slides.length, + totalDataSize, + storageType: result.storage || 'unknown', + compressionApplied: result.compressionSummary?.compressedSlides > 0, + storageMode: githubService.useMemoryStorage ? 'memory' : 'github' + }); + + const response = { + message: 'PPT saved successfully', + pptId, + slidesCount: slides.length, + savedAt: new Date().toISOString(), + totalFileSize: `${(totalDataSize / 1024).toFixed(2)} KB`, + storageType: result.storage || 'unknown', + storageMode: githubService.useMemoryStorage ? 'memory' : 'github', + architecture: 'folder-based', + slideAnalysis: { + totalSlides: slides.length, + largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, + largeSlides: largeSlides.length, + averageSlideSize: `${(totalDataSize / slides.length / 1024).toFixed(2)} KB` + } + }; + + // 添加压缩信息 + if (result.compressionSummary) { + response.compression = { + applied: result.compressionSummary.compressedSlides > 0, + compressedSlides: result.compressionSummary.compressedSlides, + totalSlides: result.compressionSummary.totalSlides, + savedSlides: result.compressionSummary.savedSlides, + failedSlides: result.compressionSummary.failedSlides, + originalSize: `${(result.compressionSummary.totalOriginalSize / 1024).toFixed(2)} KB`, + finalSize: `${(result.compressionSummary.totalFinalSize / 1024).toFixed(2)} KB`, + savedSpace: `${((result.compressionSummary.totalOriginalSize - result.compressionSummary.totalFinalSize) / 1024).toFixed(2)} KB`, + compressionRatio: `${(result.compressionSummary.compressionRatio * 100).toFixed(1)}%` + }; + + if (result.compressionSummary.compressedSlides > 0) { + response.message = `PPT saved successfully with ${result.compressionSummary.compressedSlides} slides optimized for storage`; + response.optimization = `Automatic compression applied to ${result.compressionSummary.compressedSlides} large slides, saving ${response.compression.savedSpace}`; + } + + // 添加保存警告信息 + if (result.compressionSummary.failedSlides > 0) { + response.warning = `${result.compressionSummary.failedSlides} slides failed to save`; + response.partialSave = true; + response.savedSlides = result.compressionSummary.savedSlides; + response.failedSlides = result.compressionSummary.failedSlides; + + if (result.compressionSummary.errors) { + response.slideErrors = result.compressionSummary.errors; + } + } + } + + // 添加存储路径信息 + if (result.folderPath) { + response.storagePath = result.folderPath; + response.architecture = 'folder-based (meta.json + individual slide files)'; + } + + // 添加警告信息 + if (result.warnings) { + response.warnings = result.warnings; + } + + // 记录PPT修改,用于自动备份 + try { + const { default: autoBackupService } = await import('../services/autoBackupService.js'); + autoBackupService.recordPPTModification(userId, pptId); + } catch (backupError) { + console.warn('⚠️ Failed to record PPT modification for auto backup:', backupError.message); + } + + // 🔄 新增:自动更新持久化图片链接 + try { + const { default: persistentImageLinkService } = await import('../services/persistentImageLinkService.js'); + + // 异步更新所有页面的持久化链接(不阻塞响应) + setImmediate(async () => { + try { + console.log(`🔄 Starting persistent image links update for PPT ${pptId}`); + const updateResults = await persistentImageLinkService.updateAllPersistentLinksWithFrontendExport( + userId, + pptId, + slides, + { + format: 'jpg', + quality: 0.9, + viewportSize: viewportSize || 1000, + viewportRatio: viewportRatio || 0.5625 + } + ); + + const successCount = updateResults.filter(r => r.success).length; + const failCount = updateResults.filter(r => !r.success).length; + + console.log(`✅ Persistent image links update completed: ${successCount} success, ${failCount} failed`); + + if (failCount > 0) { + console.warn(`⚠️ Some persistent image links failed to update:`, + updateResults.filter(r => !r.success).map(r => `Page ${r.pageIndex}: ${r.error}`) + ); + } + } catch (updateError) { + console.error('❌ Failed to update persistent image links:', updateError.message); + } + }); + + // 添加持久化链接信息到响应 + response.persistentLinks = { + enabled: true, + updateTriggered: true, + message: 'Persistent image links update started in background' + }; + + } catch (persistentError) { + console.warn('⚠️ Failed to trigger persistent image links update:', persistentError.message); + response.persistentLinks = { + enabled: false, + error: persistentError.message + }; + } + + res.json(response); + + } catch (saveError) { + console.error(`❌ Save operation failed:`, { + error: saveError.message, + stack: saveError.stack, + userId, + pptId, + slidesCount: slides.length, + totalDataSize, + errorName: saveError.name, + errorCode: saveError.code + }); + + let errorResponse = { + error: 'PPT save failed', + details: saveError.message, + pptId: pptId, + slidesCount: slides.length, + totalFileSize: `${(totalDataSize / 1024).toFixed(2)} KB`, + timestamp: new Date().toISOString(), + slideAnalysis: { + largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, + largeSlides: largeSlides.length + } + }; + + // 根据错误类型提供具体建议 + if (saveError.message.includes('File too large') || saveError.message.includes('exceeds')) { + errorResponse.error = 'Slide file too large for storage'; + errorResponse.suggestion = 'Some slides are too large even after compression. Try reducing image sizes or slide complexity.'; + errorResponse.limits = { + maxSlideSize: '999 KB per slide', + largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, + oversizeSlides: largeSlides.length + }; + return res.status(413).json(errorResponse); + } + + if (saveError.message.includes('Failed to compress slide')) { + errorResponse.error = 'Slide compression failed'; + errorResponse.suggestion = 'Unable to compress slides to acceptable size. Try manually reducing content complexity.'; + errorResponse.compressionError = true; + return res.status(500).json(errorResponse); + } + + if (saveError.message.includes('GitHub API') || saveError.message.includes('GitHub')) { + errorResponse.error = 'GitHub storage service error'; + errorResponse.suggestion = 'Temporary GitHub service issue. Please try again in a few minutes.'; + errorResponse.githubError = true; + return res.status(502).json(errorResponse); + } + + if (saveError.message.includes('No GitHub repositories')) { + errorResponse.error = 'Storage configuration error'; + errorResponse.suggestion = 'GitHub repositories not properly configured. Please contact support.'; + errorResponse.configError = true; + return res.status(500).json(errorResponse); + } + + // 网络相关错误 + if (saveError.code === 'ETIMEDOUT' || saveError.message.includes('timeout')) { + errorResponse.error = 'Storage operation timeout'; + errorResponse.suggestion = 'The save operation took too long. Try reducing file size or try again later.'; + errorResponse.timeoutError = true; + return res.status(504).json(errorResponse); + } + + // 默认错误 + errorResponse.suggestion = 'Unknown save error. Please try again or contact support if the problem persists.'; + errorResponse.unknownError = true; + return res.status(500).json(errorResponse); + } + + } catch (error) { + console.error(`❌ PPT save failed for user ${req.user?.userId}, PPT ID: ${req.body?.pptId}:`, { + error: error.message, + stack: error.stack, + userId: req.user?.userId, + pptId: req.body?.pptId, + requestSize: req.get('content-length'), + slidesCount: req.body?.slides?.length, + errorName: error.name, + errorCode: error.code + }); + + // 提供更详细的错误处理 + let errorResponse = { + error: 'PPT save processing failed', + details: error.message, + timestamp: new Date().toISOString(), + userId: req.user?.userId, + pptId: req.body?.pptId, + architecture: 'folder-based' + }; + + if (error.message.includes('Invalid slide data')) { + errorResponse.error = 'Invalid slide data detected'; + errorResponse.suggestion = 'One or more slides contain invalid or corrupted data'; + return res.status(400).json(errorResponse); + } + + if (error.message.includes('JSON')) { + errorResponse.error = 'Invalid data format'; + errorResponse.suggestion = 'Check slide data structure and content'; + return res.status(400).json(errorResponse); + } + + errorResponse.suggestion = 'Unknown processing error. Please try again or contact support if the problem persists.'; + next(error); + } +}); + +// 创建新PPT +router.post('/create', async (req, res, next) => { + try { + const userId = req.user.userId; + const { title } = req.body; + + if (!title) { + return res.status(400).json({ error: 'Title is required' }); + } + + const pptId = uuidv4(); + const now = new Date().toISOString(); + + // 确保数据格式与前端store一致 + const pptData = { + id: pptId, + title, + theme: { + backgroundColor: '#ffffff', + themeColor: '#d14424', + fontColor: '#333333', + fontName: 'Microsoft YaHei' + }, + slides: [ + { + id: uuidv4(), + elements: [ + { + type: 'text', + id: uuidv4(), + left: 150, + top: 200, + width: 600, + height: 100, + content: title, + fontSize: 32, + fontName: 'Microsoft YaHei', + defaultColor: '#333333', + bold: true, + align: 'center' + } + ], + background: { + type: 'solid', + color: '#ffffff' + } + } + ], + // 确保视口信息与前端一致 + viewportSize: 1000, + viewportRatio: 0.5625, + createdAt: now, + updatedAt: now + }; + + const fileName = `${pptId}.json`; + + // 优先使用Huggingface存储服务保存PPT数据 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + try { + // 尝试保存到Huggingface存储 + await huggingfaceStorageService.storePPTData(userId, pptId, pptData); + console.log(`✅ PPT created and saved to Huggingface storage: ${pptId}`); + } catch (hfError) { + console.warn('⚠️ Huggingface storage unavailable, falling back to GitHub:', hfError.message); + // 回退到GitHub存储 + console.log(`Creating PPT for user ${userId}, using ${githubService.useMemoryStorage ? 'memory' : 'GitHub'} storage`); + + if (githubService.useMemoryStorage) { + await githubService.saveToMemory(userId, fileName, pptData); + } else { + await githubService.saveFile(userId, fileName, pptData); + } + } + + console.log(`PPT created successfully: ${pptId}`); + res.json({ success: true, pptId, pptData }); + } catch (error) { + console.error('PPT creation error:', error); + next(error); + } +}); + +// 删除PPT +router.delete('/:pptId', async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.params; + const fileName = `${pptId}.json`; + let deleted = false; + + // 优先从Huggingface存储删除 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + try { + await huggingfaceStorageService.deletePPTData(userId, pptId); + deleted = true; + console.log(`✅ PPT deleted from Huggingface storage: ${pptId}`); + } catch (hfError) { + console.warn('⚠️ Failed to delete from Huggingface storage, trying other storage:', hfError.message); + + if (githubService.useMemoryStorage) { + // 内存存储模式 + deleted = githubService.memoryStorage.delete(`users/${userId}/${fileName}`); + if (deleted) { + console.log(`✅ PPT deleted from memory storage: ${pptId}`); + } + } else { + // GitHub存储模式 - 从所有仓库中删除 + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot delete from GitHub storage'); + } else { + for (let i = 0; i < githubService.repositories.length; i++) { + try { + await githubService.deleteFile(userId, fileName, i); + deleted = true; + console.log(`✅ PPT deleted from GitHub storage: ${pptId}`); + break; // 只需要从一个仓库删除成功即可 + } catch (error) { + // 继续尝试其他仓库 + continue; + } + } + } + } + } + + if (!deleted) { + return res.status(404).json({ error: 'PPT not found in any storage' }); + } + + res.json({ message: 'PPT deleted successfully' }); + } catch (error) { + next(error); + } +}); + +// 复制PPT +router.post('/:pptId/copy', async (req, res, next) => { + try { + const userId = req.user.userId; + const { pptId } = req.params; + const { title } = req.body; + const sourceFileName = `${pptId}.json`; + + // 获取源PPT数据 - 优先从Huggingface存储读取 + let sourcePPT = null; + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + try { + // 尝试从Huggingface存储读取 + sourcePPT = await huggingfaceStorageService.getPPTData(userId, pptId); + console.log('✅ Source PPT loaded from Huggingface storage for copying'); + } catch (hfError) { + console.warn('⚠️ Failed to load source PPT from Huggingface storage, trying other storage:', hfError.message); + + if (githubService.useMemoryStorage) { + // 内存存储模式 + sourcePPT = await githubService.getFromMemory(userId, sourceFileName); + if (sourcePPT) { + console.log('✅ Source PPT loaded from memory storage for copying'); + } + } else { + // GitHub存储模式 + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot load from GitHub storage'); + } else { + for (let i = 0; i < githubService.repositories.length; i++) { + try { + const result = await githubService.getFile(userId, sourceFileName, i); + if (result) { + sourcePPT = result; + console.log('✅ Source PPT loaded from GitHub storage for copying'); + break; + } + } catch (error) { + continue; + } + } + } + } + } + + if (!sourcePPT) { + return res.status(404).json({ error: 'Source PPT not found' }); + } + + // 创建新的PPT ID和数据 + const newPptId = uuidv4(); + const newFileName = `${newPptId}.json`; + const newPPTData = { + ...sourcePPT, + id: newPptId, + title: title || `${sourcePPT.title} - 副本`, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + // 保存复制的PPT - 优先保存到Huggingface存储 + try { + await huggingfaceStorageService.storePPTData(userId, newPptId, newPPTData); + console.log(`✅ Copied PPT saved to Huggingface storage: ${newPptId}`); + } catch (hfError) { + console.warn('⚠️ Failed to save copied PPT to Huggingface storage, using fallback:', hfError.message); + + if (githubService.useMemoryStorage) { + await githubService.saveToMemory(userId, newFileName, newPPTData); + console.log(`✅ Copied PPT saved to memory storage: ${newPptId}`); + } else { + await githubService.saveFile(userId, newFileName, newPPTData, 0); + console.log(`✅ Copied PPT saved to GitHub storage: ${newPptId}`); + } + } + + res.json({ + message: 'PPT copied successfully', + pptId: newPptId, + ppt: newPPTData + }); + } catch (error) { + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/public.js b/backend/src/routes/public.js new file mode 100644 index 0000000000000000000000000000000000000000..11f8df017538ff7f18fd199e0542c27fd7200b15 --- /dev/null +++ b/backend/src/routes/public.js @@ -0,0 +1,557 @@ +import express from 'express'; +// import screenshotService from '../services/screenshotService.js'; // 已禁用 +import githubService from '../services/githubService.js'; +import { generateSlideHTML } from '../utils/htmlGenerator.js'; + +const router = express.Router(); + +// SVG功能已被完全删除 + +// Generate error page for frontend display +function generateErrorPage(title, message) { + return ` + + + + + + ${title} - PPT导出 + + + +
+
+
${title}
+
${message}
+
+ + 回到首页 +
+
+ + + `; +} + +// Publicly access PPT page - return HTML page +router.get('/view/:userId/:pptId/:slideIndex?', async (req, res, next) => { + try { + const { userId, pptId, slideIndex = 0 } = req.params; + const querySlideIndex = req.query.slide ? parseInt(req.query.slide) : parseInt(slideIndex); + + let pptData = null; + + try { + // 尝试从Huggingface存储读取 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + console.log('✅ PPT data loaded from Huggingface storage for view'); + } catch (hfError) { + console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); + + // 回退到GitHub存储 + const fileName = `${pptId}.json`; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); + } else { + // Try all GitHub repositories + for (let i = 0; i < githubService.repositories.length; i++) { + try { + const result = await githubService.getFile(userId, fileName, i); + if (result) { + pptData = result.content; + console.log(`✅ PPT data loaded from GitHub repository ${i} for view`); + break; + } + } catch (error) { + continue; + } + } + } + } + + if (!pptData) { + return res.status(404).send(generateErrorPage('PPT Not Found', 'Please check if the link is correct')); + } + + const slideIdx = querySlideIndex; + if (slideIdx >= pptData.slides.length || slideIdx < 0) { + console.log(`❌ Invalid slide index: ${slideIndex} (parsed: ${slideIdx}), PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1})`); + return res.status(404).send(generateErrorPage('Slide Not Found', `Slide ${slideIndex} not found. PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1}).`)); + } + + // 使用共享模块生成HTML + const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'view' }); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(htmlContent); + } catch (error) { + next(error); + } +}); + +// API endpoint: Get PPT data and specified slide (JSON format) +router.get('/api-view/:userId/:pptId/:slideIndex?', async (req, res, next) => { + try { + const { userId, pptId, slideIndex = 0 } = req.params; + + let pptData = null; + + try { + // 尝试从Huggingface存储读取 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + console.log('✅ PPT data loaded from Huggingface storage for api-view'); + } catch (hfError) { + console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); + + // 回退到GitHub存储 + const fileName = `${pptId}.json`; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); + } else { + // Try all GitHub repositories + for (let i = 0; i < githubService.repositories.length; i++) { + try { + const result = await githubService.getFile(userId, fileName, i); + if (result) { + pptData = result.content; + console.log(`✅ PPT data loaded from GitHub repository ${i} for api-view`); + break; + } + } catch (error) { + continue; + } + } + } + } + + if (!pptData) { + return res.status(404).json({ error: 'PPT not found' }); + } + + const slideIdx = parseInt(slideIndex); + + // 验证 slideIndex 是否为有效数字 + if (isNaN(slideIdx)) { + console.log(`❌ Invalid slide index format: ${slideIndex}`); + const errorPage = generateErrorPage('Invalid Slide Index', `Slide index "${slideIndex}" is not a valid number.`); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + return res.status(400).send(errorPage); + } + + // 安全检查:确保 slides 数组存在 + if (!pptData.slides || !Array.isArray(pptData.slides)) { + console.log(`❌ Invalid PPT data: slides array not found`); + const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + return res.status(400).send(errorPage); + } + + if (slideIdx >= pptData.slides.length || slideIdx < 0) { + console.log(`❌ Invalid slide index: ${slideIndex} (parsed: ${slideIdx}), PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1})`); + return res.status(404).json({ + error: 'Slide not found', + details: `Slide ${slideIndex} not found. PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1}).`, + slideIndex: slideIdx, + totalSlides: pptData.slides.length, + validRange: `0-${pptData.slides.length - 1}` + }); + } + + // Return PPT data and specified slide + res.json({ + id: pptData.id, + title: pptData.title, + theme: pptData.theme, + currentSlide: pptData.slides[slideIdx], + slideIndex: slideIdx, + totalSlides: pptData.slides.length, + isPublicView: true + }); + } catch (error) { + next(error); + } +}); + +// Get complete PPT data (read-only mode) +router.get('/ppt/:userId/:pptId', async (req, res, next) => { + try { + const { userId, pptId } = req.params; + + let pptData = null; + + try { + // 尝试从Huggingface存储读取 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + console.log('✅ PPT data loaded from Huggingface storage for ppt endpoint'); + } catch (hfError) { + console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); + + // 回退到GitHub存储 + const fileName = `${pptId}.json`; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); + } else { + // Try all GitHub repositories + for (let i = 0; i < githubService.repositories.length; i++) { + try { + const result = await githubService.getFile(userId, fileName, i); + if (result) { + pptData = result.content; + console.log(`✅ PPT data loaded from GitHub repository ${i} for ppt endpoint`); + break; + } + } catch (error) { + continue; + } + } + } + } + + if (!pptData) { + return res.status(404).json({ error: 'PPT not found' }); + } + + // Return read-only version of PPT data + res.json({ + ...pptData, + isPublicView: true, + readOnly: true + }); + } catch (error) { + next(error); + } +}); + +// Generate PPT share link +router.post('/generate-share-link', async (req, res, next) => { + try { + const { userId, pptId, slideIndex = 0 } = req.body; + + if (!userId || !pptId) { + return res.status(400).json({ error: 'User ID and PPT ID are required' }); + } + + console.log(`Generating share link for PPT: ${pptId}, User: ${userId}`); + + // Verify if PPT exists - 优先从Huggingface存储读取 + let pptData = null; + + try { + // 尝试从Huggingface存储读取 + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + pptData = await huggingfaceStorageService.getPPTData(userId, pptId); + console.log('✅ PPT data loaded from Huggingface storage for share link generation'); + } catch (hfError) { + console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); + + // 回退到GitHub存储 + const fileName = `${pptId}.json`; + + // Check if GitHub repositories are configured + if (!githubService.repositories || githubService.repositories.length === 0) { + console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); + } else { + console.log(`Checking ${githubService.repositories.length} GitHub repositories...`); + + for (let i = 0; i < githubService.repositories.length; i++) { + try { + console.log(`Checking repository ${i}: ${githubService.repositories[i]}`); + const result = await githubService.getFile(userId, fileName, i); + if (result) { + pptData = result.content; + console.log(`✅ PPT data loaded from GitHub repository ${i}`); + break; + } + } catch (error) { + console.log(`PPT not found in repository ${i}: ${error.message}`); + continue; + } + } + } + } + + if (!pptData) { + return res.status(404).json({ + error: 'PPT not found', + details: `PPT ${pptId} not found for user ${userId}`, + searchedLocations: 'Huggingface storage and GitHub repositories' + }); + } + + const baseUrl = process.env.PUBLIC_URL || req.get('host'); + // 修复协议判断:localhost 始终使用 http,生产环境使用 https + const protocol = baseUrl.includes('localhost') ? 'http' : 'https'; + + const shareLinks = { + // Single page share link + slideUrl: `${protocol}://${baseUrl}/api/public/view/${userId}/${pptId}/${slideIndex}`, + // Complete PPT share link + pptUrl: `${protocol}://${baseUrl}/api/public/ppt/${userId}/${pptId}`, + // Frontend view link + viewUrl: `${protocol}://${baseUrl}/public/${userId}/${pptId}/${slideIndex}`, + // 图片链接 - 支持内存图片访问 + screenshotUrl: `${protocol}://${baseUrl}/api/public/image/${userId}/${pptId}/${slideIndex}`, + directImageUrl: `${protocol}://${baseUrl}/api/public/direct-image/${userId}/${pptId}/${slideIndex}`, + metadata: { + title: pptData.title || 'Untitled PPT', + slideCount: pptData.slides?.length || 0, + currentSlide: slideIndex, + generatedAt: new Date().toISOString(), + note: 'Image links support memory-based image access with fallback to error SVG' + } + }; + + console.log('✅ Share links generated successfully'); + res.json(shareLinks); + } catch (error) { + console.error('❌ Error generating share link:', error); + next(error); + } +}); + +// 公开图片访问路由 - 处理 /image/:userId/:linkId/:pageIndex 格式 +router.get('/image/:userId/:linkId/:pageIndex', async (req, res, next) => { + try { + const { userId, linkId, pageIndex } = req.params; + const pageIdx = parseInt(pageIndex); + + console.log(`📷 Public image request: userId=${userId}, linkId=${linkId}, pageIndex=${pageIndex}`); + + // 首先尝试从内存存储中获取图片 + try { + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + // 检查内存中是否有该图片 + if (huggingfaceStorageService.hasPublicImage(userId, linkId, pageIdx)) { + console.log(`✅ Found image in memory storage: ${userId}/${linkId}/${pageIdx}`); + + const imageResult = await huggingfaceStorageService.getPublicImage(userId, linkId, pageIdx); + + // 设置响应头 + const format = imageResult.metadata.format || 'png'; + res.setHeader('Content-Type', `image/${format}`); + res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时 + res.setHeader('Content-Length', imageResult.data.length); + + // 返回图片数据 + return res.send(imageResult.data); + } + } catch (memoryError) { + console.log(`⚠️ Memory storage access failed: ${memoryError.message}`); + } + + // 检查linkId是否为UUID格式(持久化链接) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + + if (uuidRegex.test(linkId)) { + // 检查这是否真的是一个持久化链接 + try { + const { default: persistentImageLinkService } = await import('../services/persistentImageLinkService.js'); + + // 确保服务已初始化 + if (!persistentImageLinkService.initialized) { + await persistentImageLinkService.initialize(); + } + + // 检查持久化链接是否存在 + const linkExists = persistentImageLinkService.persistentLinks.has(linkId); + + if (linkExists) { + // 这是真正的持久化图片链接,重定向到正确的持久化图片API + console.log(`🔗 Redirecting persistent image request: ${linkId}`); + return res.redirect(`/api/persistent-images/${linkId}`); + } else { + // UUID格式但不是持久化链接,当作PPT ID处理 + console.log(`⚠️ UUID format but not a persistent link, treating as PPT ID: ${linkId}`); + } + } catch (error) { + console.log(`⚠️ Error checking persistent link, treating as PPT ID: ${error.message}`); + } + } + + // 内存中没有找到图片,且不是持久化链接,生成错误SVG图片 + console.log(`⚠️ Image not found in memory storage, generating error SVG for pptId: ${linkId}`); + + // 生成错误提示SVG + const errorSvg = ` + + 图片未找到 + PPT ID: ${linkId} + 页面: ${pageIndex} + 请先生成图片或使用HTML预览 + `; + + res.setHeader('Content-Type', 'image/svg+xml'); + res.setHeader('Cache-Control', 'no-cache'); + return res.send(errorSvg); + } catch (error) { + next(error); + } +}); + +// 传统图片路由(3个参数)- 已禁用 +router.get('/image/:userId/:pptId', async (req, res, next) => { + try { + console.log('❌ Image generation feature has been disabled'); + + // 对于图片请求,返回适当的错误响应而不是HTML页面 + res.status(410).json({ + error: 'Feature Disabled', + message: 'Image generation functionality has been disabled. Please use the HTML view instead.', + note: 'This endpoint no longer generates images. Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' + }); + } catch (error) { + next(error); + } +}); + +// 公开访问:获取用户的PPT列表(仅包含内存中有图片的PPT) +router.get('/ppt-list/:userId', async (req, res, next) => { + try { + const { userId } = req.params; + + console.log(`📋 Public PPT list request for user: ${userId}`); + + try { + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + // 获取用户的PPT列表(仅包含内存中有图片的PPT) + const pptList = huggingfaceStorageService.getPublicPPTList(userId); + + console.log(`✅ Found ${pptList.length} PPTs with images for user ${userId}`); + + res.json({ + success: true, + userId, + pptCount: pptList.length, + ppts: pptList, + note: 'Only PPTs with images stored in memory are listed' + }); + } catch (error) { + console.error(`❌ Failed to get PPT list for user ${userId}:`, error); + res.status(500).json({ + success: false, + error: 'Failed to retrieve PPT list', + message: error.message + }); + } + } catch (error) { + next(error); + } +}); + +// 直接持久化图片链接路由 - 处理 /direct-image/:userId/:linkId/:pageIndex 格式 +router.get('/direct-image/:userId/:linkId/:pageIndex', async (req, res, next) => { + try { + const { userId, linkId, pageIndex } = req.params; + const pageIdx = parseInt(pageIndex); + + // 首先尝试从内存存储中获取图片 + try { + const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); + + // 检查内存中是否有该图片 + if (huggingfaceStorageService.hasPublicImage(userId, linkId, pageIdx)) { + console.log(`✅ Found direct image in memory storage: ${userId}/${linkId}/${pageIdx}`); + + const imageResult = await huggingfaceStorageService.getPublicImage(userId, linkId, pageIdx); + + // 设置响应头 + const format = imageResult.metadata.format || 'png'; + res.setHeader('Content-Type', `image/${format}`); + res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时 + res.setHeader('Content-Length', imageResult.data.length); + + // 返回图片数据 + return res.send(imageResult.data); + } + } catch (memoryError) { + console.log(`⚠️ Memory storage access failed: ${memoryError.message}`); + } + + // 检查linkId是否为UUID格式(持久化链接) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + + if (uuidRegex.test(linkId)) { + // 这是持久化图片链接,重定向到正确的持久化图片API + console.log(`🔗 Redirecting direct persistent image request: ${linkId}`); + return res.redirect(`/api/persistent-images/${linkId}`); + } else { + // 内存中没有找到图片,且不是持久化链接 + console.log('❌ Direct image not found in memory storage'); + + // 对于图片请求,返回适当的错误响应而不是HTML页面 + res.status(404).json({ + error: 'Image Not Found', + message: 'Image not found in memory storage. Please generate the image first or use the HTML view instead.', + note: 'Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' + }); + } + } catch (error) { + next(error); + } +}); + +// 传统直接图片路由(3个参数)- 已禁用 +router.get('/direct-image/:userId/:pptId', async (req, res, next) => { + try { + console.log('❌ Direct image generation feature has been disabled'); + + // 对于图片请求,返回适当的错误响应而不是HTML页面 + res.status(410).json({ + error: 'Feature Disabled', + message: 'Direct image generation functionality has been disabled. Please use the HTML view instead.', + note: 'This endpoint no longer generates images. Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' + }); + } catch (error) { + next(error); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/services/autoBackupService.js b/backend/src/services/autoBackupService.js new file mode 100644 index 0000000000000000000000000000000000000000..2a67807ec104b9daa2d0e8ea2a27482138e66906 --- /dev/null +++ b/backend/src/services/autoBackupService.js @@ -0,0 +1,348 @@ +import cron from 'node-cron'; +import githubService from './githubService.js'; +import huggingfaceStorageService from './huggingfaceStorageService.js'; +import fs from 'fs/promises'; +import path from 'path'; + +/** + * 自动备份服务 + * 负责定时将修改的PPT数据同步到GitHub仓库 + */ +class AutoBackupService { + constructor() { + this.isInitialized = false; + this.cronJob = null; + this.backupInterval = '0 */8 * * *'; // 每8小时执行一次 + this.lastBackupTime = null; + this.backupStats = { + totalBackups: 0, + successfulBackups: 0, + failedBackups: 0, + lastBackupResult: null + }; + + // 跟踪修改的PPT数据 + this.modifiedPPTs = new Map(); // userId -> Set of pptIds + this.lastModificationCheck = new Date(); + + console.log('🔄 AutoBackupService initialized'); + } + + /** + * 初始化自动备份服务 + */ + async initialize() { + try { + console.log('🚀 Initializing AutoBackupService...'); + + // 加载上次备份时间 + await this.loadBackupState(); + + // 启动定时任务 + this.startCronJob(); + + this.isInitialized = true; + console.log('✅ AutoBackupService initialized successfully'); + console.log(`⏰ Next backup scheduled: ${this.getNextBackupTime()}`); + + } catch (error) { + console.error('❌ Failed to initialize AutoBackupService:', error); + throw error; + } + } + + /** + * 启动定时任务 + */ + startCronJob() { + if (this.cronJob) { + this.cronJob.stop(); + } + + console.log(`⏰ Starting auto backup cron job: ${this.backupInterval}`); + + this.cronJob = cron.schedule(this.backupInterval, async () => { + console.log('🔄 Auto backup triggered by cron job'); + await this.performAutoBackup(); + }, { + scheduled: true, + timezone: 'UTC' + }); + + console.log('✅ Auto backup cron job started'); + } + + /** + * 停止定时任务 + */ + stopCronJob() { + if (this.cronJob) { + this.cronJob.stop(); + this.cronJob = null; + console.log('🛑 Auto backup cron job stopped'); + } + } + + /** + * 记录PPT修改 + */ + recordPPTModification(userId, pptId) { + if (!this.modifiedPPTs.has(userId)) { + this.modifiedPPTs.set(userId, new Set()); + } + + this.modifiedPPTs.get(userId).add(pptId); + console.log(`📝 Recorded modification: User ${userId}, PPT ${pptId}`); + } + + /** + * 获取修改的PPT列表 + */ + getModifiedPPTs() { + const result = {}; + for (const [userId, pptIds] of this.modifiedPPTs.entries()) { + result[userId] = Array.from(pptIds); + } + return result; + } + + /** + * 清除修改记录 + */ + clearModificationRecords() { + this.modifiedPPTs.clear(); + console.log('🧹 Cleared modification records'); + } + + /** + * 执行自动备份 + */ + async performAutoBackup() { + const startTime = new Date(); + console.log(`🔄 Starting auto backup at ${startTime.toISOString()}`); + + const backupResult = { + timestamp: startTime, + success: false, + totalUsers: 0, + totalPPTs: 0, + successfulBackups: 0, + failedBackups: 0, + errors: [], + duration: 0 + }; + + try { + this.backupStats.totalBackups++; + + // 获取修改的PPT列表 + const modifiedPPTs = this.getModifiedPPTs(); + const userIds = Object.keys(modifiedPPTs); + + backupResult.totalUsers = userIds.length; + + if (userIds.length === 0) { + console.log('ℹ️ No modified PPTs found, skipping backup'); + backupResult.success = true; + return backupResult; + } + + console.log(`📊 Found ${userIds.length} users with modified PPTs`); + + // 为每个用户备份修改的PPT + for (const userId of userIds) { + const pptIds = modifiedPPTs[userId]; + backupResult.totalPPTs += pptIds.length; + + console.log(`👤 Backing up ${pptIds.length} PPTs for user ${userId}`); + + for (const pptId of pptIds) { + try { + // 获取PPT数据 + const pptData = await githubService.getPPT(userId, pptId); + + if (!pptData) { + console.warn(`⚠️ PPT ${pptId} not found for user ${userId}`); + backupResult.failedBackups++; + continue; + } + + // 执行备份到GitHub + await githubService.savePPT(userId, pptId, pptData); + + console.log(`✅ Successfully backed up PPT ${pptId} for user ${userId}`); + backupResult.successfulBackups++; + + } catch (error) { + console.error(`❌ Failed to backup PPT ${pptId} for user ${userId}:`, error); + backupResult.failedBackups++; + backupResult.errors.push({ + userId, + pptId, + error: error.message + }); + } + } + } + + // 清除修改记录 + this.clearModificationRecords(); + + backupResult.success = backupResult.failedBackups === 0; + + if (backupResult.success) { + this.backupStats.successfulBackups++; + console.log(`✅ Auto backup completed successfully: ${backupResult.successfulBackups} PPTs backed up`); + } else { + this.backupStats.failedBackups++; + console.error(`❌ Auto backup completed with errors: ${backupResult.failedBackups} failures`); + } + + } catch (error) { + console.error('❌ Auto backup failed:', error); + backupResult.success = false; + backupResult.errors.push({ + type: 'system', + error: error.message + }); + this.backupStats.failedBackups++; + } finally { + const endTime = new Date(); + backupResult.duration = endTime.getTime() - startTime.getTime(); + + this.lastBackupTime = startTime; + this.backupStats.lastBackupResult = backupResult; + + // 保存备份状态 + await this.saveBackupState(); + + console.log(`🏁 Auto backup finished in ${backupResult.duration}ms`); + } + + return backupResult; + } + + /** + * 手动触发备份 + */ + async triggerManualBackup() { + console.log('🔄 Manual backup triggered'); + return await this.performAutoBackup(); + } + + /** + * 获取下次备份时间 + */ + getNextBackupTime() { + if (!this.cronJob) { + return 'Not scheduled'; + } + + // 计算下次执行时间(简化版本) + const now = new Date(); + const nextHour = Math.ceil(now.getHours() / 8) * 8; + const nextBackup = new Date(now); + + if (nextHour >= 24) { + nextBackup.setDate(nextBackup.getDate() + 1); + nextBackup.setHours(0, 0, 0, 0); + } else { + nextBackup.setHours(nextHour, 0, 0, 0); + } + + return nextBackup.toISOString(); + } + + /** + * 获取备份统计信息 + */ + getBackupStats() { + return { + ...this.backupStats, + lastBackupTime: this.lastBackupTime, + nextBackupTime: this.getNextBackupTime(), + modifiedPPTsCount: Array.from(this.modifiedPPTs.values()) + .reduce((total, pptSet) => total + pptSet.size, 0), + isRunning: !!this.cronJob, + backupInterval: this.backupInterval + }; + } + + /** + * 加载备份状态 + */ + async loadBackupState() { + try { + const stateFile = path.join(process.cwd(), 'data', 'backup-state.json'); + + try { + const stateData = await fs.readFile(stateFile, 'utf8'); + const state = JSON.parse(stateData); + + this.lastBackupTime = state.lastBackupTime ? new Date(state.lastBackupTime) : null; + this.backupStats = { ...this.backupStats, ...state.backupStats }; + + console.log('📂 Loaded backup state from file'); + } catch (error) { + if (error.code !== 'ENOENT') { + console.warn('⚠️ Failed to load backup state:', error.message); + } + } + } catch (error) { + console.warn('⚠️ Failed to load backup state:', error.message); + } + } + + /** + * 保存备份状态 + */ + async saveBackupState() { + try { + const stateFile = path.join(process.cwd(), 'data', 'backup-state.json'); + const stateDir = path.dirname(stateFile); + + // 确保目录存在 + await fs.mkdir(stateDir, { recursive: true }); + + const state = { + lastBackupTime: this.lastBackupTime, + backupStats: this.backupStats, + savedAt: new Date().toISOString() + }; + + await fs.writeFile(stateFile, JSON.stringify(state, null, 2)); + console.log('💾 Saved backup state to file'); + + } catch (error) { + console.warn('⚠️ Failed to save backup state:', error.message); + } + } + + /** + * 更新备份间隔 + */ + updateBackupInterval(cronExpression) { + this.backupInterval = cronExpression; + console.log(`⏰ Updated backup interval to: ${cronExpression}`); + + // 重启定时任务 + this.startCronJob(); + } + + /** + * 清理服务 + */ + async cleanup() { + console.log('🧹 Cleaning up AutoBackupService...'); + + this.stopCronJob(); + await this.saveBackupState(); + + console.log('✅ AutoBackupService cleanup completed'); + } +} + +// 创建单例实例 +const autoBackupService = new AutoBackupService(); + +export default autoBackupService; \ No newline at end of file diff --git a/backend/src/services/backupSchedulerService.js b/backend/src/services/backupSchedulerService.js new file mode 100644 index 0000000000000000000000000000000000000000..7bd1068d5ae35dfc21cd7b414e28e8191be2c697 --- /dev/null +++ b/backend/src/services/backupSchedulerService.js @@ -0,0 +1,466 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import githubService from './githubService.js'; +import huggingfaceStorageService from './huggingfaceStorageService.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * 备份调度服务 + * 管理用户数据的定时备份到GitHub和服务器重启时的数据恢复 + */ +class BackupSchedulerService { + constructor() { + this.backupQueue = new Map(); // userId -> { timer, scheduledTime } + this.isRestoring = false; + this.backupInProgress = new Set(); // 正在备份的用户ID + this.initialized = false; + + // 备份配置 + this.BACKUP_DELAY = 8 * 60 * 60 * 1000; // 8小时 + this.MAX_BACKUP_RETRIES = 3; + this.BACKUP_RETRY_DELAY = 5 * 60 * 1000; // 5分钟 + } + + /** + * 初始化备份调度服务 + */ + async initialize() { + if (this.initialized) return; + + try { + // 服务器启动时检查是否需要从GitHub恢复数据 + await this.checkAndRestoreFromGitHub(); + + this.initialized = true; + console.log('✅ BackupSchedulerService initialized successfully'); + } catch (error) { + console.error('❌ Failed to initialize BackupSchedulerService:', error); + throw error; + } + } + + /** + * 调度用户数据备份 + * @param {string} userId - 用户ID + * @param {number} delay - 延迟时间(毫秒),默认8小时 + */ + scheduleUserBackup(userId, delay = this.BACKUP_DELAY) { + if (!this.initialized) { + console.warn('BackupSchedulerService not initialized, skipping backup schedule'); + return; + } + + // 如果已经有调度,先取消 + this.cancelUserBackup(userId); + + const scheduledTime = Date.now() + delay; + + const timer = setTimeout(async () => { + try { + await this.backupUserData(userId); + } catch (error) { + console.error(`❌ Scheduled backup failed for user ${userId}:`, error); + // 重试机制 + this.retryBackup(userId, 1); + } finally { + this.backupQueue.delete(userId); + } + }, delay); + + this.backupQueue.set(userId, { timer, scheduledTime }); + + console.log(`⏰ Backup scheduled for user ${userId} in ${Math.round(delay / 1000 / 60)} minutes`); + } + + /** + * 取消用户备份调度 + * @param {string} userId - 用户ID + */ + cancelUserBackup(userId) { + const backup = this.backupQueue.get(userId); + if (backup) { + clearTimeout(backup.timer); + this.backupQueue.delete(userId); + console.log(`❌ Backup cancelled for user ${userId}`); + } + } + + /** + * 立即备份用户数据 + * @param {string} userId - 用户ID + * @returns {Promise} 备份结果 + */ + async backupUserData(userId) { + if (this.backupInProgress.has(userId)) { + console.log(`⏳ Backup already in progress for user ${userId}`); + return false; + } + + this.backupInProgress.add(userId); + + try { + console.log(`🔄 Starting backup for user ${userId}`); + + // 获取用户的所有数据 + const userData = await this.collectUserData(userId); + + if (!userData || Object.keys(userData).length === 0) { + console.log(`📭 No data to backup for user ${userId}`); + return true; + } + + // 备份到GitHub + const backupResult = await this.backupToGitHub(userId, userData); + + if (backupResult.success) { + console.log(`✅ Backup completed for user ${userId}`); + return true; + } else { + throw new Error(backupResult.error || 'Backup failed'); + } + } catch (error) { + console.error(`❌ Backup failed for user ${userId}:`, error); + throw error; + } finally { + this.backupInProgress.delete(userId); + } + } + + /** + * 重试备份 + * @param {string} userId - 用户ID + * @param {number} attempt - 重试次数 + */ + retryBackup(userId, attempt) { + if (attempt > this.MAX_BACKUP_RETRIES) { + console.error(`❌ Max backup retries exceeded for user ${userId}`); + return; + } + + console.log(`🔄 Retrying backup for user ${userId} (attempt ${attempt}/${this.MAX_BACKUP_RETRIES})`); + + setTimeout(async () => { + try { + await this.backupUserData(userId); + } catch (error) { + this.retryBackup(userId, attempt + 1); + } + }, this.BACKUP_RETRY_DELAY * attempt); // 递增延迟 + } + + /** + * 收集用户数据 + * @param {string} userId - 用户ID + * @returns {Promise} 用户数据 + */ + async collectUserData(userId) { + try { + const userData = { + userId, + backupTime: new Date().toISOString(), + ppts: {}, + images: [] + }; + + // 获取用户的PPT数据 + const pptList = await githubService.getUserPPTList(userId); + + for (const ppt of pptList) { + try { + const pptData = await githubService.getPPT(userId, ppt.id); + if (pptData && pptData.content) { + userData.ppts[ppt.id] = { + id: ppt.id, + title: ppt.title, + content: pptData.content, + updatedAt: ppt.updatedAt, + createdAt: ppt.createdAt + }; + } + } catch (error) { + console.warn(`⚠️ Failed to get PPT ${ppt.id} for backup:`, error.message); + } + } + + // 获取用户的图片数据 + const userImages = await huggingfaceStorageService.getUserImages(userId); + userData.images = userImages; + + return userData; + } catch (error) { + console.error(`❌ Failed to collect user data for ${userId}:`, error); + throw error; + } + } + + /** + * 备份数据到GitHub + * @param {string} userId - 用户ID + * @param {Object} userData - 用户数据 + * @returns {Promise} 备份结果 + */ + async backupToGitHub(userId, userData) { + try { + const backupFileName = `backup-${userId}-${Date.now()}.json`; + const backupPath = `backups/${userId}/${backupFileName}`; + + // 压缩数据 + const compressedData = JSON.stringify(userData); + + // 上传到GitHub + const result = await githubService.uploadFile( + backupPath, + compressedData, + `Backup user data for ${userId}`, + 0 // 使用第一个仓库进行备份 + ); + + if (result.success) { + // 更新备份索引 + await this.updateBackupIndex(userId, { + fileName: backupFileName, + path: backupPath, + timestamp: Date.now(), + size: compressedData.length, + pptCount: Object.keys(userData.ppts).length, + imageCount: userData.images.length + }); + + return { success: true, path: backupPath }; + } else { + return { success: false, error: result.error }; + } + } catch (error) { + return { success: false, error: error.message }; + } + } + + /** + * 更新备份索引 + * @param {string} userId - 用户ID + * @param {Object} backupInfo - 备份信息 + */ + async updateBackupIndex(userId, backupInfo) { + try { + const indexPath = `backups/${userId}/index.json`; + + // 获取现有索引 + let index = { backups: [] }; + try { + const existingIndex = await githubService.getFile(indexPath, 0); + if (existingIndex.success) { + index = JSON.parse(existingIndex.content); + } + } catch (error) { + // 索引不存在,使用默认值 + } + + // 添加新备份记录 + index.backups.unshift(backupInfo); + + // 保留最近10个备份记录 + index.backups = index.backups.slice(0, 10); + + // 更新索引文件 + await githubService.uploadFile( + indexPath, + JSON.stringify(index, null, 2), + `Update backup index for ${userId}`, + 0 + ); + } catch (error) { + console.warn(`⚠️ Failed to update backup index for ${userId}:`, error.message); + } + } + + /** + * 检查并从GitHub恢复数据 + */ + async checkAndRestoreFromGitHub() { + if (this.isRestoring) return; + + this.isRestoring = true; + + try { + console.log('🔍 Checking if data restoration is needed...'); + + // 检查Huggingface存储是否为空或需要恢复 + const storageStats = await huggingfaceStorageService.getStorageStats(); + + if (storageStats.totalImages === 0) { + console.log('📦 No existing data found, attempting to restore from GitHub...'); + await this.restoreAllDataFromGitHub(); + } else { + console.log(`📊 Found ${storageStats.totalImages} existing images, skipping restoration`); + } + } catch (error) { + console.error('❌ Failed to check/restore data from GitHub:', error); + } finally { + this.isRestoring = false; + } + } + + /** + * 从GitHub恢复所有数据 + */ + async restoreAllDataFromGitHub() { + try { + console.log('🔄 Starting data restoration from GitHub...'); + + // 获取所有备份用户 + const backupUsers = await this.getBackupUsers(); + + let restoredCount = 0; + + for (const userId of backupUsers) { + try { + const restored = await this.restoreUserDataFromGitHub(userId); + if (restored) { + restoredCount++; + } + } catch (error) { + console.warn(`⚠️ Failed to restore data for user ${userId}:`, error.message); + } + } + + console.log(`✅ Data restoration completed. Restored ${restoredCount} users.`); + } catch (error) { + console.error('❌ Failed to restore all data from GitHub:', error); + throw error; + } + } + + /** + * 获取所有有备份的用户 + * @returns {Promise} 用户ID数组 + */ + async getBackupUsers() { + try { + // 尝试获取备份目录列表 + const backupDirs = await githubService.listDirectory('backups', 0); + + if (backupDirs.success && backupDirs.items) { + return backupDirs.items + .filter(item => item.type === 'dir') + .map(item => item.name); + } + + return []; + } catch (error) { + console.warn('⚠️ Failed to get backup users:', error.message); + return []; + } + } + + /** + * 从GitHub恢复用户数据 + * @param {string} userId - 用户ID + * @returns {Promise} 恢复结果 + */ + async restoreUserDataFromGitHub(userId) { + try { + console.log(`🔄 Restoring data for user ${userId}...`); + + // 获取用户的备份索引 + const indexPath = `backups/${userId}/index.json`; + const indexResult = await githubService.getFile(indexPath, 0); + + if (!indexResult.success) { + console.log(`📭 No backup index found for user ${userId}`); + return false; + } + + const index = JSON.parse(indexResult.content); + + if (!index.backups || index.backups.length === 0) { + console.log(`📭 No backups found for user ${userId}`); + return false; + } + + // 获取最新的备份 + const latestBackup = index.backups[0]; + const backupResult = await githubService.getFile(latestBackup.path, 0); + + if (!backupResult.success) { + console.error(`❌ Failed to get backup file for user ${userId}`); + return false; + } + + const userData = JSON.parse(backupResult.content); + + // 恢复PPT数据 + let restoredPPTs = 0; + for (const [pptId, pptData] of Object.entries(userData.ppts || {})) { + try { + await githubService.savePPT(userId, pptId, pptData.content); + restoredPPTs++; + } catch (error) { + console.warn(`⚠️ Failed to restore PPT ${pptId}:`, error.message); + } + } + + console.log(`✅ Restored ${restoredPPTs} PPTs for user ${userId}`); + + // 注意:图片数据需要重新生成,因为备份中只有元数据 + console.log(`ℹ️ Image data for user ${userId} will be regenerated on demand`); + + return true; + } catch (error) { + console.error(`❌ Failed to restore user data for ${userId}:`, error); + return false; + } + } + + /** + * 获取备份状态 + * @returns {Object} 备份状态信息 + */ + getBackupStatus() { + const scheduledBackups = []; + + for (const [userId, backup] of this.backupQueue.entries()) { + scheduledBackups.push({ + userId, + scheduledTime: new Date(backup.scheduledTime).toISOString(), + remainingTime: Math.max(0, backup.scheduledTime - Date.now()) + }); + } + + return { + initialized: this.initialized, + isRestoring: this.isRestoring, + scheduledBackups: scheduledBackups.length, + backupsInProgress: this.backupInProgress.size, + scheduledBackupDetails: scheduledBackups + }; + } + + /** + * 清理资源 + */ + cleanup() { + // 取消所有调度的备份 + for (const [userId] of this.backupQueue.entries()) { + this.cancelUserBackup(userId); + } + + console.log('✅ BackupSchedulerService cleaned up'); + } +} + +// 创建单例实例 +const backupSchedulerService = new BackupSchedulerService(); + +// 优雅关闭处理 +process.on('SIGTERM', () => { + backupSchedulerService.cleanup(); +}); + +process.on('SIGINT', () => { + backupSchedulerService.cleanup(); +}); + +export default backupSchedulerService; \ No newline at end of file diff --git a/backend/src/services/githubService.js b/backend/src/services/githubService.js new file mode 100644 index 0000000000000000000000000000000000000000..7f43fc9d3f80a01597f113d9e8fb3d85cd5a3d9a --- /dev/null +++ b/backend/src/services/githubService.js @@ -0,0 +1,1418 @@ +import axios from 'axios'; + +class GitHubService { + constructor() { + // 延迟初始化,动态读取环境变量 + this.initialized = false; + this.useMemoryStorage = false; + this.memoryStorage = new Map(); + this.apiUrl = 'https://api.github.com'; + + // 首次使用时再初始化 + this.initPromise = null; + + // 添加错误重试配置 + this.retryConfig = { + maxRetries: 3, + retryDelay: 1000, + backoffMultiplier: 2 + }; + + // API限制处理 + this.apiRateLimit = { + requestQueue: [], + processing: false, + minDelay: 100 // 最小请求间隔 + }; + } + + // 动态初始化方法 + async initialize() { + if (this.initialized) { + return; + } + + console.log('=== GitHub Service Configuration ==='); + + try { + // 动态读取环境变量 + this.token = process.env.GITHUB_TOKEN; + this.repositories = process.env.GITHUB_REPOS + ? process.env.GITHUB_REPOS.split(',').map(repo => repo.trim()) + : []; + + console.log('Token configured:', !!this.token); + console.log('Token preview:', this.token ? `${this.token.substring(0, 8)}...` : 'Not set'); + console.log('Repositories:', this.repositories); + console.log('Repositories length:', this.repositories?.length || 0); + + // Check if token is a placeholder + if (!this.token || this.token === 'your_github_token_here') { + console.warn('❌ GitHub token is missing or using placeholder value!'); + console.warn('⚠️ Switching to memory storage mode for development...'); + this.useMemoryStorage = true; + console.log('✅ Memory storage mode activated'); + this.initialized = true; + return; + } + + if (!this.repositories || this.repositories.length === 0) { + console.warn('❌ No GitHub repositories configured!'); + console.warn('⚠️ Switching to memory storage mode...'); + this.useMemoryStorage = true; + console.log('✅ Memory storage mode activated'); + this.initialized = true; + return; + } + + // Check for placeholder repository URLs + const validRepos = this.repositories.filter(repo => { + const isPlaceholder = repo.includes('your-username') || repo.includes('placeholder'); + if (isPlaceholder) { + console.warn(`⚠️ Skipping placeholder repository: ${repo}`); + return false; + } + return this.isValidRepoUrl(repo); + }); + + if (validRepos.length === 0) { + console.warn('❌ No valid GitHub repositories found (all are placeholders)!'); + console.warn('⚠️ Switching to memory storage mode...'); + this.useMemoryStorage = true; + console.log('✅ Memory storage mode activated'); + this.initialized = true; + return; + } + + this.repositories = validRepos; + + // 验证每个仓库URL的格式 + this.repositories.forEach((repo, index) => { + if (!this.isValidRepoUrl(repo)) { + console.error(`❌ Invalid repository URL at index ${index}: ${repo}`); + throw new Error(`Invalid repository URL: ${repo}. Expected format: https://github.com/owner/repo`); + } + }); + + console.log('✅ GitHub Service initialized successfully'); + this.initialized = true; + } catch (error) { + console.error('❌ GitHub Service initialization failed:', error); + console.warn('⚠️ Falling back to memory storage mode...'); + this.useMemoryStorage = true; + this.initialized = true; + } + } + + // 确保在所有方法中先初始化 + async ensureInitialized() { + if (!this.initPromise) { + this.initPromise = this.initialize(); + } + await this.initPromise; + } + + // 验证仓库URL格式 + isValidRepoUrl(repoUrl) { + const githubUrlPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/; + return githubUrlPattern.test(repoUrl.trim()); + } + + // 带重试的API请求方法 + async makeGitHubRequest(requestFn, operation = 'GitHub API request', maxRetries = null) { + const retries = maxRetries || this.retryConfig.maxRetries; + let lastError = null; + + for (let attempt = 0; attempt <= retries; attempt++) { + try { + if (attempt > 0) { + const delay = this.retryConfig.retryDelay * Math.pow(this.retryConfig.backoffMultiplier, attempt - 1); + console.log(`🔄 Retrying ${operation} (attempt ${attempt + 1}/${retries + 1}) after ${delay}ms...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + + const result = await requestFn(); + + if (attempt > 0) { + console.log(`✅ ${operation} succeeded on retry attempt ${attempt + 1}`); + } + + return result; + } catch (error) { + lastError = error; + + // 判断是否应该重试 + if (!this.shouldRetry(error) || attempt === retries) { + break; + } + + console.warn(`⚠️ ${operation} failed (attempt ${attempt + 1}):`, error.message); + } + } + + console.error(`❌ ${operation} failed after ${retries + 1} attempts:`, lastError.message); + throw lastError; + } + + // 判断错误是否应该重试 + shouldRetry(error) { + if (!error.response) { + return true; // 网络错误,重试 + } + + const status = error.response.status; + + // 这些状态码不应该重试 + if ([400, 401, 403, 404, 422].includes(status)) { + return false; + } + + // 5xx错误和429限制错误应该重试 + if (status >= 500 || status === 429) { + return true; + } + + return false; + } + + // 验证GitHub连接 + async validateConnection() { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + console.log('📝 Memory storage mode active'); + return { + valid: true, + useMemoryStorage: true, + repositories: [], + message: 'Using memory storage mode for development' + }; + } + + try { + // 测试GitHub API连接 + const response = await this.makeGitHubRequest(async () => { + return await axios.get(`${this.apiUrl}/user`, { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + }); + }, 'GitHub user authentication'); + + console.log('GitHub API connection successful:', response.data.login); + + // 测试仓库访问和权限 + const repoResults = []; + for (const repoUrl of this.repositories) { + try { + const { owner, repo } = this.parseRepoUrl(repoUrl); + + // 检查仓库基本信息 + const repoResponse = await this.makeGitHubRequest(async () => { + return await axios.get(`${this.apiUrl}/repos/${owner}/${repo}`, { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + }); + }, `Repository access check for ${owner}/${repo}`); + + // 检查权限 + const permissions = repoResponse.data.permissions; + console.log(`Repository permissions for ${owner}/${repo}:`, permissions); + + // 测试是否可以访问仓库内容 + let canAccessContents = false; + let repoEmpty = false; + try { + await axios.get(`${this.apiUrl}/repos/${owner}/${repo}/contents`, { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 15000 + }); + canAccessContents = true; + } catch (contentsError) { + console.log(`Cannot access repository contents: ${contentsError.message}`); + + // 如果是409错误,说明仓库为空 + if (contentsError.response?.status === 409) { + repoEmpty = true; + console.log('Repository appears to be empty, will initialize when needed'); + } + } + + repoResults.push({ + url: repoUrl, + accessible: true, + name: repoResponse.data.full_name, + permissions, + canAccessContents, + repoEmpty, + defaultBranch: repoResponse.data.default_branch, + size: repoResponse.data.size + }); + } catch (error) { + console.error(`Repository access error for ${repoUrl}:`, error.response?.data || error.message); + repoResults.push({ url: repoUrl, accessible: false, error: error.message }); + } + } + + return { valid: true, user: response.data.login, repositories: repoResults }; + } catch (error) { + console.error('GitHub connection validation failed:', error.message); + return { valid: false, reason: error.message }; + } + } + + // 获取仓库信息 + parseRepoUrl(repoUrl) { + const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/); + if (!match) throw new Error('Invalid GitHub repository URL'); + return { owner: match[1], repo: match[2] }; + } + + // 初始化空仓库 + async initializeRepository(repoIndex = 0) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + console.log('📝 Memory storage mode - no repository initialization needed'); + return { success: true, message: 'Memory storage initialized' }; + } + + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + try { + console.log(`🚀 Initializing repository: ${owner}/${repo}`); + + // 创建初始README文件 + const readmeContent = `# PPT Storage Repository\n\nThis repository is used to store PPT data files.\n\nCreated: ${new Date().toISOString()}`; + const content = Buffer.from(readmeContent).toString('base64'); + + await this.makeGitHubRequest(async () => { + return await axios.put( + `${this.apiUrl}/repos/${owner}/${repo}/contents/README.md`, + { + message: 'Initialize PPT storage repository', + content: content + }, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Repository initialization for ${owner}/${repo}`); + + console.log(`✅ Repository ${owner}/${repo} initialized successfully`); + return { success: true, message: 'Repository initialized' }; + } catch (error) { + console.error(`❌ Repository initialization failed:`, error); + throw new Error(`Failed to initialize repository: ${error.message}`); + } + } + + // 兼容性方法:旧的getFile方法重定向到新的getPPT + async getFile(userId, fileName, repoIndex = 0) { + await this.ensureInitialized(); + + const pptId = fileName.replace('.json', ''); + return await this.getPPT(userId, pptId, repoIndex); + } + + // 兼容性方法:旧的saveFile方法重定向到新的savePPT + async saveFile(userId, fileName, data, repoIndex = 0) { + await this.ensureInitialized(); + + const pptId = fileName.replace('.json', ''); + return await this.savePPT(userId, pptId, data, repoIndex); + } + + // 数据格式标准化 + normalizeDataFormat(data) { + if (!data || typeof data !== 'object') { + throw new Error('Invalid data format provided'); + } + + const normalized = { + id: data.id || data.pptId || `ppt-${Date.now()}`, + title: data.title || '未命名演示文稿', + slides: Array.isArray(data.slides) ? data.slides : [], + theme: data.theme || { + backgroundColor: '#ffffff', + themeColor: '#d14424', + fontColor: '#333333', + fontName: 'Microsoft YaHei' + }, + viewportSize: data.viewportSize || 1000, + viewportRatio: data.viewportRatio || 0.5625, + createdAt: data.createdAt || new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + // 标准化slides数据 + normalized.slides = normalized.slides.map((slide, index) => { + if (!slide || typeof slide !== 'object') { + return { + id: `slide-${index}`, + elements: [], + background: { type: 'solid', color: '#ffffff' } + }; + } + + return { + id: slide.id || `slide-${index}`, + elements: Array.isArray(slide.elements) ? slide.elements : [], + background: slide.background || { type: 'solid', color: '#ffffff' }, + ...slide + }; + }); + + // 保留原始数据的其他字段 + Object.keys(data).forEach(key => { + if (!normalized.hasOwnProperty(key) && key !== 'slides') { + normalized[key] = data[key]; + } + }); + + return normalized; + } + + // 兼容性:旧的deleteFile方法 + async deleteFile(userId, fileName, repoIndex = 0) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + const key = `users/${userId}/${fileName}`; + return this.memoryStorage.delete(key); + } + + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + const path = `users/${userId}/${fileName}`; + + try { + // 获取文件信息 + const response = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Get file info for deletion: ${fileName}`); + + // 删除主文件 + await this.makeGitHubRequest(async () => { + return await axios.delete( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${path}`, + { + data: { + message: `Delete legacy PPT: ${fileName}`, + sha: response.data.sha + }, + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Delete legacy file: ${fileName}`); + + console.log(`✅ Legacy file deleted: ${fileName}`); + return true; + } catch (error) { + if (error.response?.status === 404) { + console.log(`📄 File not found: ${fileName}`); + return false; + } + console.error(`❌ Delete failed for ${fileName}:`, error); + throw new Error(`Failed to delete file: ${error.message}`); + } + } + + // Memory storage methods - 兼容性方法 + async saveToMemory(userId, fileName, data) { + const pptId = fileName.replace('.json', ''); + return await this.savePPTToMemory(userId, pptId, data); + } + + async getFromMemory(userId, fileName) { + const pptId = fileName.replace('.json', ''); + const result = await this.getPPTFromMemory(userId, pptId); + return result ? result.content : null; + } + + // 新架构:获取PPT(文件夹模式) - 简化版本 + async getPPT(userId, pptId, repoIndex = 0) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + return await this.getPPTFromMemory(userId, pptId); + } + + try { + console.log(`📂 Getting PPT folder: ${pptId} for user: ${userId}`); + + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + const pptFolderPath = `users/${userId}/${pptId}`; + + // 1. 获取PPT文件夹内容 - 带重试 + const folderResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${pptFolderPath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Get PPT folder contents for ${pptId}`); + + // 2. 读取元数据文件 + const metaFile = folderResponse.data.find(file => file.name === 'meta.json'); + if (!metaFile) { + throw new Error('PPT metadata file not found'); + } + + const metaResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${metaFile.path}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 15000 + } + ); + }, `Get PPT metadata for ${pptId}`); + + let metadata; + try { + const metaContent = Buffer.from(metaResponse.data.content, 'base64').toString('utf8'); + metadata = JSON.parse(metaContent); + } catch (parseError) { + console.error('❌ Failed to parse metadata:', parseError); + throw new Error('Invalid PPT metadata format'); + } + + // 3. 加载所有slide文件 + const allFiles = folderResponse.data; + const slides = []; + const failedSlides = []; + + // 查找slide文件(只处理普通文件,忽略拆分文件) + const slideFiles = allFiles.filter(file => + file.name.startsWith('slide_') && + file.name.endsWith('.json') && + !file.name.includes('_main') && + !file.name.includes('_part_') + ); + + // 按序号排序 + const sortedSlideFiles = slideFiles.sort((a, b) => { + const aIndex = parseInt(a.name.match(/slide_(\d+)\.json/)?.[1] || '0'); + const bIndex = parseInt(b.name.match(/slide_(\d+)\.json/)?.[1] || '0'); + return aIndex - bIndex; + }); + + for (const slideFile of sortedSlideFiles) { + try { + const slide = await this.loadSlideFile(slideFile, repoIndex); + const slideIndex = parseInt(slideFile.name.match(/slide_(\d+)\.json/)?.[1] || '0'); + slides[slideIndex] = slide; + } catch (slideError) { + console.warn(`⚠️ Failed to load slide ${slideFile.name}:`, slideError.message); + failedSlides.push(slideFile.name); + } + } + + // 4. 组装完整PPT数据 + const finalSlides = slides.filter(slide => slide !== undefined); + const pptData = { + ...metadata, + slides: finalSlides, + storage: { + type: 'folder', + slidesCount: finalSlides.length, + folderPath: pptFolderPath, + loadedAt: new Date().toISOString(), + failedSlides: failedSlides.length > 0 ? failedSlides : undefined + } + }; + + if (failedSlides.length > 0) { + console.warn(`⚠️ PPT loaded with ${failedSlides.length} failed slides`); + } else { + console.log(`✅ PPT loaded successfully: ${finalSlides.length} slides`); + } + + return { content: pptData }; + + } catch (error) { + if (error.response?.status === 404) { + console.log(`📄 PPT folder not found: ${pptId}`); + return null; + } + console.error(`❌ Get PPT failed:`, error); + throw new Error(`Failed to get PPT: ${error.message}`); + } + } + + // 新增:检查文件大小 + async checkFileSize(filePath, repoIndex) { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + try { + const response = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${filePath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 15000 + } + ); + }, `Check file size for ${filePath}`); + + return { + size: response.data.size, + sha: response.data.sha + }; + } catch (error) { + if (error.response?.status === 404) { + return null; + } + throw error; + } + } + + // 新增:智能读取文件内容(根据大小选择策略) + async readFileContent(filePath, repoIndex, maxDirectSize = 1024 * 1024) { // 1MB限制 + const fileInfo = await this.checkFileSize(filePath, repoIndex); + + if (!fileInfo) { + return null; + } + + // 如果文件小于限制,直接读取 + if (fileInfo.size <= maxDirectSize) { + return await this.readFileContentDirect(filePath, repoIndex); + } + + // 大文件:使用Git Blob API读取 + console.log(`📊 Large file detected (${(fileInfo.size / 1024 / 1024).toFixed(2)} MB), using blob API`); + return await this.readFileContentViaBlob(filePath, fileInfo.sha, repoIndex); + } + + // 直接读取文件(小文件) + async readFileContentDirect(filePath, repoIndex) { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + const response = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${filePath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Read file content directly: ${filePath}`); + + const content = Buffer.from(response.data.content, 'base64').toString('utf8'); + return JSON.parse(content); + } + + // 通过Git Blob API读取大文件 + async readFileContentViaBlob(filePath, sha, repoIndex) { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + try { + console.log(`🔍 Reading large file via blob API: ${filePath}`); + + const response = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/git/blobs/${sha}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 120000 // 2分钟超时 + } + ); + }, `Read blob for large file: ${filePath}`); + + if (response.data.encoding === 'base64') { + const content = Buffer.from(response.data.content, 'base64').toString('utf8'); + return JSON.parse(content); + } else { + // 如果不是base64编码,直接解析 + return JSON.parse(response.data.content); + } + } catch (error) { + console.error(`❌ Blob API read failed for ${filePath}:`, error.message); + + // 回退到分批读取策略 + console.log(`🔄 Falling back to chunked reading...`); + return await this.readLargeFileInChunks(filePath, repoIndex); + } + } + + // 分批读取大文件(最后的回退策略) + async readLargeFileInChunks(filePath, repoIndex) { + console.log(`📦 Attempting chunked read for: ${filePath}`); + + // 检查是否存在分块文件(PPT文件夹结构) + const folderPath = filePath.replace(/\/[^\/]+$/, ''); + + try { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + // 尝试读取文件夹内容 + const folderResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${folderPath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Read folder for chunked file: ${folderPath}`); + + // 查找相关的分块文件 + const chunkFiles = folderResponse.data.filter(file => + file.name.includes('_part_') || file.name.includes('_chunk_') + ); + + if (chunkFiles.length > 0) { + console.log(`📊 Found ${chunkFiles.length} chunk files, reassembling...`); + return await this.reassembleChunkedFiles(chunkFiles, repoIndex); + } + + throw new Error('No chunked files found, cannot read large file'); + } catch (error) { + console.error(`❌ Chunked read failed:`, error.message); + throw new Error(`Unable to read large file: ${filePath}. File may be too large for current GitHub API limits.`); + } + } + + // 重组分块文件 + async reassembleChunkedFiles(chunkFiles, repoIndex) { + const chunks = []; + + // 按顺序读取所有分块 + const sortedChunks = chunkFiles.sort((a, b) => { + const aIndex = parseInt(a.name.match(/_(\d+)(?:_|\.)/)?.[1] || '0'); + const bIndex = parseInt(b.name.match(/_(\d+)(?:_|\.)/)?.[1] || '0'); + return aIndex - bIndex; + }); + + for (const chunkFile of sortedChunks) { + try { + const chunkContent = await this.readFileContentDirect(chunkFile.path, repoIndex); + chunks.push(chunkContent); + } catch (error) { + console.error(`⚠️ Failed to read chunk ${chunkFile.name}:`, error.message); + } + } + + // 重组数据 + if (chunks.length === 0) { + throw new Error('No valid chunks found'); + } + + // 假设第一个chunk包含基础结构 + const baseData = chunks[0]; + const allSlides = []; + + chunks.forEach(chunk => { + if (chunk.slides && Array.isArray(chunk.slides)) { + allSlides.push(...chunk.slides); + } + }); + + return { + ...baseData, + slides: allSlides, + storage: { + type: 'reassembled', + chunksCount: chunks.length, + reassembledAt: new Date().toISOString() + } + }; + } + + // 修改现有的loadSlideFile方法,使用新的智能读取 + async loadSlideFile(slideFile, repoIndex) { + try { + // 使用智能读取策略 + const slideContent = await this.readFileContent(slideFile.path, repoIndex); + return slideContent; + } catch (error) { + console.error(`❌ Failed to load slide file ${slideFile.name}:`, error.message); + throw error; + } + } + + // slide压缩算法 - 保留轻量压缩 + async compressSlide(slide) { + let compressedSlide = JSON.parse(JSON.stringify(slide)); // 深拷贝 + + // 压缩策略1:移除不必要的属性 + if (compressedSlide.elements) { + compressedSlide.elements = compressedSlide.elements.map(element => { + // 移除编辑状态属性 + element = this.removeUnnecessaryProps(element); + + // 压缩文本内容(移除多余空白) + if (element.type === 'text' && element.content && typeof element.content === 'string') { + element.content = element.content.replace(/\s+/g, ' ').trim(); + } + + return element; + }); + } + + // 压缩策略2:精简数值精度 + compressedSlide = this.roundNumericValues(compressedSlide); + + const compressedSize = Buffer.byteLength(JSON.stringify(compressedSlide), 'utf8'); + console.log(`🗜️ Light compression applied: ${(compressedSize / 1024).toFixed(2)} KB`); + + return compressedSlide; + } + + // 移除不必要的属性 + removeUnnecessaryProps(element) { + const unnecessaryProps = [ + 'selected', 'editing', 'dragData', 'resizeData', + 'tempData', 'cache', 'debug' + ]; + + unnecessaryProps.forEach(prop => { + if (element[prop] !== undefined) { + delete element[prop]; + } + }); + + return element; + } + + // 精简数值精度 - 保持高精度的关键属性 + roundNumericValues(obj, precision = 2) { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(item => this.roundNumericValues(item, precision)); + } + + const rounded = {}; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'number') { + // 🎯 关键布局属性保持高精度 + if (['left', 'top', 'width', 'height', 'x', 'y', 'viewportRatio', 'viewportSize'].includes(key)) { + rounded[key] = Math.round(value * 1000000000) / 1000000000; // 9位精度 + } + // 🎯 变换和旋转属性保持高精度 + else if (key.includes('transform') || key.includes('rotate') || key === 'rotate') { + rounded[key] = Math.round(value * 1000000000) / 1000000000; + } + // 📐 其他数值属性适度精简 + else { + rounded[key] = typeof value === 'number' && !isNaN(value) && isFinite(value) + ? Math.round(value * 1000000) / 1000000 // 6位精度 + : value; + } + } else if (typeof value === 'object') { + rounded[key] = this.roundNumericValues(value, precision); + } else { + rounded[key] = value; + } + } + + return rounded; + } + + // 通用文件保存到仓库 - 简化版本 + async saveFileToRepo(filePath, data, commitMessage, repoIndex) { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + + try { + // 验证数据 + if (!data || typeof data !== 'object') { + throw new Error('Invalid data provided for file save'); + } + + const content = Buffer.from(JSON.stringify(data)).toString('base64'); + const fileSize = Buffer.byteLength(JSON.stringify(data), 'utf8'); + + // 检查文件大小(GitHub限制100MB) + if (fileSize > 100 * 1024 * 1024) { + throw new Error(`File too large: ${(fileSize / 1024 / 1024).toFixed(2)} MB exceeds GitHub's 100MB limit`); + } + + console.log(`💾 Saving file: ${filePath} (${(fileSize / 1024).toFixed(2)} KB)`); + + // 检查文件是否已存在 + let sha = null; + try { + const existingResponse = await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${filePath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + sha = existingResponse.data.sha; + } catch (error) { + // 文件不存在,将创建新文件 + } + + // 保存文件 + const response = await axios.put( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${filePath}`, + { + message: commitMessage, + content: content, + ...(sha && { sha }) + }, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 60000 + } + ); + + console.log(`✅ File saved successfully: ${filePath}`); + return response.data; + } catch (error) { + console.error(`❌ File save failed for ${filePath}:`, error.message); + + if (error.response?.status === 422) { + if (error.response?.data?.message?.includes('file is too large')) { + throw new Error(`File too large: ${filePath} exceeds GitHub size limits`); + } + if (error.response?.data?.message?.includes('Invalid request')) { + throw new Error(`Invalid file format for: ${filePath}`); + } + } + + if (error.response?.status === 409) { + throw new Error(`File conflict for: ${filePath}. File may have been modified by another process.`); + } + + throw new Error(`Failed to save file ${filePath}: ${error.message}`); + } + } + + // 单个slide保存 - 简化版本 + async saveSlideWithCompression(filePath, slide, slideIndex, repoIndex) { + try { + // 应用轻量压缩 + const compressedSlide = await this.compressSlide(slide); + + await this.makeGitHubRequest(async () => { + return await this.saveFileToRepo( + filePath, + compressedSlide, + `Save slide ${slideIndex}`, + repoIndex + ); + }, `Save slide ${slideIndex}`); + + const finalSize = Buffer.byteLength(JSON.stringify(compressedSlide), 'utf8'); + + return { + slideIndex, + finalSize, + compressed: true + }; + } catch (error) { + console.error(`❌ Failed to save slide ${slideIndex}:`, error); + throw error; + } + } + + // 新架构:保存PPT(文件夹模式) - 简化版本 + async savePPT(userId, pptId, pptData, repoIndex = 0) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + return await this.savePPTToMemory(userId, pptId, pptData); + } + + try { + console.log(`📂 Saving PPT to folder: ${pptId} for user: ${userId}`); + + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + const pptFolderPath = `users/${userId}/${pptId}`; + + // 验证输入数据 + if (!pptData || typeof pptData !== 'object') { + throw new Error('Invalid PPT data provided'); + } + + // 1. 准备元数据(不包含slides) + const metadata = { + id: pptData.id || pptId, + title: pptData.title || '未命名演示文稿', + theme: pptData.theme || { + backgroundColor: '#ffffff', + themeColor: '#d14424', + fontColor: '#333333', + fontName: 'Microsoft YaHei' + }, + viewportSize: pptData.viewportSize || 1000, + viewportRatio: pptData.viewportRatio || 0.5625, + createdAt: pptData.createdAt || new Date().toISOString(), + updatedAt: new Date().toISOString(), + storage: { + type: 'folder', + version: '2.0', + slidesCount: pptData.slides?.length || 0 + } + }; + + // 2. 保存元数据文件 + await this.makeGitHubRequest(async () => { + return await this.saveFileToRepo( + `${pptFolderPath}/meta.json`, + metadata, + `Update PPT metadata: ${metadata.title}`, + repoIndex + ); + }, `Save metadata for PPT ${pptId}`); + + console.log(`✅ Metadata saved for PPT: ${pptId}`); + + // 3. 串行化保存每个slide文件 + const slides = Array.isArray(pptData.slides) ? pptData.slides : []; + const saveResults = []; + + console.log(`📊 Starting to save ${slides.length} slides...`); + + for (let i = 0; i < slides.length; i++) { + const slide = slides[i]; + const slideFileName = `slide_${String(i).padStart(3, '0')}.json`; + + console.log(`💾 Saving slide ${i + 1}/${slides.length}: ${slideFileName}`); + + try { + // 验证slide数据 + if (!slide || typeof slide !== 'object') { + throw new Error(`Invalid slide data at index ${i}`); + } + + const result = await this.saveSlideWithCompression( + `${pptFolderPath}/${slideFileName}`, + slide, + i, + repoIndex + ); + + saveResults.push(result); + console.log(`✅ Slide ${i} saved: ${(result.finalSize / 1024).toFixed(2)} KB`); + + // 添加延迟避免GitHub API速率限制 + if (i < slides.length - 1) { + await new Promise(resolve => setTimeout(resolve, this.apiRateLimit.minDelay)); + } + } catch (slideError) { + console.error(`❌ Failed to save slide ${i}: ${slideError.message}`); + throw slideError; // 任何slide保存失败都应该停止整个过程 + } + } + + // 4. 计算保存统计 + const totalSize = saveResults.reduce((sum, r) => sum + r.finalSize, 0); + + console.log(`🎉 PPT saved successfully: ${slides.length} slides, total size: ${(totalSize / 1024).toFixed(2)} KB`); + + return { + success: true, + pptId: pptId, + storage: 'folder', + slidesCount: slides.length, + folderPath: pptFolderPath, + size: totalSize + }; + + } catch (error) { + console.error(`❌ PPT save failed for ${pptId}: ${error.message}`); + throw new Error(`Failed to save PPT: ${error.message}`); + } + } + + // Memory storage methods - 简化版本 + async getPPTFromMemory(userId, pptId) { + const metaKey = `users/${userId}/${pptId}/meta`; + const metadata = this.memoryStorage.get(metaKey); + + if (!metadata) { + console.log(`📄 PPT not found in memory: ${pptId}`); + return null; + } + + // 重组slides + const slides = []; + const slidesCount = metadata.storage?.slidesCount || 0; + + for (let i = 0; i < slidesCount; i++) { + const slideKey = `users/${userId}/${pptId}/slide_${String(i).padStart(3, '0')}`; + const slide = this.memoryStorage.get(slideKey); + if (slide) { + slides.push(slide); + } + } + + const pptData = { + ...metadata, + slides: slides, + storage: { + ...metadata.storage, + loadedAt: new Date().toISOString() + } + }; + + console.log(`📖 Read PPT from memory: ${pptId} (${slides.length} slides)`); + return { content: pptData }; + } + + // 简化内存存储 + async savePPTToMemory(userId, pptId, pptData) { + // 保存元数据 + const metadata = { + id: pptData.id || pptId, + title: pptData.title || '未命名演示文稿', + theme: pptData.theme, + viewportSize: pptData.viewportSize || 1000, + viewportRatio: pptData.viewportRatio || 0.5625, + createdAt: pptData.createdAt || new Date().toISOString(), + updatedAt: new Date().toISOString(), + storage: { + type: 'folder', + version: '2.0', + slidesCount: pptData.slides?.length || 0 + } + }; + + const metaKey = `users/${userId}/${pptId}/meta`; + this.memoryStorage.set(metaKey, metadata); + + // 保存slides + const slides = pptData.slides || []; + let totalSize = 0; + + for (let i = 0; i < slides.length; i++) { + const slide = slides[i]; + const compressedSlide = await this.compressSlide(slide); + const slideKey = `users/${userId}/${pptId}/slide_${String(i).padStart(3, '0')}`; + this.memoryStorage.set(slideKey, compressedSlide); + + totalSize += Buffer.byteLength(JSON.stringify(compressedSlide), 'utf8'); + } + + console.log(`💾 Saved PPT to memory: ${pptId} (${slides.length} slides, ${(totalSize / 1024).toFixed(2)} KB)`); + + return { + success: true, + storage: 'folder', + slidesCount: slides.length, + size: totalSize + }; + } + + // 删除PPT时清理所有文件 + async deletePPTFromMemory(userId, pptId) { + let deleted = 0; + const prefix = `users/${userId}/${pptId}/`; + + // 删除所有相关键 + for (const key of this.memoryStorage.keys()) { + if (key.startsWith(prefix)) { + this.memoryStorage.delete(key); + deleted++; + } + } + + console.log(`📝 Memory storage: Deleted ${deleted} files for PPT ${pptId}`); + return deleted > 0; + } + + // 更新用户PPT列表 + async getUserPPTListFromMemory(userId) { + const results = []; + const userPrefix = `users/${userId}/`; + const pptIds = new Set(); + + // 收集所有PPT ID + for (const key of this.memoryStorage.keys()) { + if (key.startsWith(userPrefix) && key.includes('/meta')) { + const pptId = key.replace(userPrefix, '').replace('/meta', ''); + pptIds.add(pptId); + } + } + + // 获取每个PPT的元数据 + for (const pptId of pptIds) { + const metaKey = `${userPrefix}${pptId}/meta`; + const metadata = this.memoryStorage.get(metaKey); + + if (metadata) { + results.push({ + name: pptId, + title: metadata.title || '未命名演示文稿', + updatedAt: metadata.updatedAt || new Date().toISOString(), + slidesCount: metadata.storage?.slidesCount || 0, + isChunked: false, + storageType: 'folder', + size: JSON.stringify(metadata).length, + repoIndex: 0 + }); + } + } + + console.log(`📋 Found ${results.length} PPTs in memory for user ${userId}`); + return results.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + } + + // 获取用户PPT列表(新架构) - 简化版本 + async getUserPPTList(userId) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + return await this.getUserPPTListFromMemory(userId); + } + + const pptList = []; + + // 检查所有仓库 + for (let repoIndex = 0; repoIndex < this.repositories.length; repoIndex++) { + try { + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + const userDirPath = `users/${userId}`; + + const response = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${userDirPath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Get user directory for ${userId} in repo ${repoIndex}`); + + // 查找PPT文件夹 + const pptFolders = response.data.filter(item => + item.type === 'dir' // PPT存储为文件夹 + ); + + for (const folder of pptFolders) { + try { + const pptId = folder.name; + + // 获取PPT元数据 + const metaResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${userDirPath}/${pptId}/meta.json`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 15000 + } + ); + }, `Get metadata for PPT ${pptId}`); + + const metaContent = Buffer.from(metaResponse.data.content, 'base64').toString('utf8'); + const metadata = JSON.parse(metaContent); + + pptList.push({ + name: pptId, + title: metadata.title || '未命名演示文稿', + updatedAt: metadata.updatedAt || new Date().toISOString(), + slidesCount: metadata.storage?.slidesCount || 0, + isChunked: false, + storageType: 'folder', + size: metaResponse.data.size || 0, + repoIndex: repoIndex + }); + } catch (error) { + console.warn(`跳过无效PPT文件夹 ${folder.name}:`, error.message); + } + } + + // 兼容性:同时检查旧的单文件格式 + const jsonFiles = response.data.filter(file => + file.type === 'file' && + file.name.endsWith('.json') && + !file.name.includes('_chunk_') + ); + + for (const file of jsonFiles) { + try { + const pptId = file.name.replace('.json', ''); + + // 避免重复添加(如果已经有文件夹版本) + if (pptList.some(p => p.name === pptId)) { + continue; + } + + const fileResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${userDirPath}/${file.name}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 15000 + } + ); + }, `Get legacy PPT file ${file.name}`); + + const content = Buffer.from(fileResponse.data.content, 'base64').toString('utf8'); + const pptData = JSON.parse(content); + + pptList.push({ + name: pptId, + title: pptData.title || '未命名演示文稿', + updatedAt: pptData.updatedAt || new Date().toISOString(), + slidesCount: pptData.isChunked ? pptData.totalSlides : (pptData.slides?.length || 0), + isChunked: pptData.isChunked || false, + storageType: 'legacy', + size: file.size, + repoIndex: repoIndex + }); + } catch (error) { + console.warn(`跳过无效文件 ${file.name}:`, error.message); + } + } + } catch (error) { + console.warn(`仓库 ${repoIndex} 中没有找到用户目录或访问失败:`, error.message); + continue; + } + } + + // 按更新时间排序 + return pptList.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + } + + // 删除PPT(新架构) - 简化版本 + async deletePPT(userId, pptId, repoIndex = 0) { + await this.ensureInitialized(); + + if (this.useMemoryStorage) { + return await this.deletePPTFromMemory(userId, pptId); + } + + const repoUrl = this.repositories[repoIndex]; + const { owner, repo } = this.parseRepoUrl(repoUrl); + const pptFolderPath = `users/${userId}/${pptId}`; + + try { + console.log(`🗑️ Deleting PPT folder: ${pptFolderPath}`); + + // 1. 获取文件夹内容 + const folderResponse = await this.makeGitHubRequest(async () => { + return await axios.get( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${pptFolderPath}`, + { + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Get PPT folder contents for deletion: ${pptId}`); + + // 2. 串行删除所有文件 + for (const file of folderResponse.data) { + try { + await this.makeGitHubRequest(async () => { + return await axios.delete( + `${this.apiUrl}/repos/${owner}/${repo}/contents/${file.path}`, + { + data: { + message: `Delete PPT file: ${file.name}`, + sha: file.sha + }, + headers: { + 'Authorization': `token ${this.token}`, + 'Accept': 'application/vnd.github.v3+json' + }, + timeout: 30000 + } + ); + }, `Delete file ${file.name}`); + + console.log(`✅ Deleted file: ${file.name}`); + + // 添加延迟避免API限制 + await new Promise(resolve => setTimeout(resolve, this.apiRateLimit.minDelay)); + } catch (error) { + console.warn(`⚠️ Failed to delete file ${file.name}:`, error.message); + } + } + + console.log(`✅ PPT folder deleted successfully: ${pptId}`); + return true; + + } catch (error) { + if (error.response?.status === 404) { + console.log(`📄 PPT folder not found, trying legacy format: ${pptId}`); + return await this.deleteFile(userId, `${pptId}.json`, repoIndex); + } + + console.error(`❌ Delete PPT failed for ${pptId}:`, error); + throw new Error(`Failed to delete PPT: ${error.message}`); + } + } +} + +export default new GitHubService(); \ No newline at end of file diff --git a/backend/src/services/huggingfaceStorageService.js b/backend/src/services/huggingfaceStorageService.js new file mode 100644 index 0000000000000000000000000000000000000000..bd5ec77d1b243f1bb423b027d37f35618c07119c --- /dev/null +++ b/backend/src/services/huggingfaceStorageService.js @@ -0,0 +1,976 @@ +import { fileURLToPath } from 'url'; +import { v4 as uuidv4 } from 'uuid'; +import crypto from 'crypto'; +import path from 'path'; +import fs from 'fs/promises'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + + + +/** + * Huggingface存储服务 + * 管理PPT图片文件的内存存储、链接生成和版本控制 + * 利用Huggingface Space的16G内存进行临时存储 + */ +class HuggingfaceStorageService { + constructor() { + // 文件系统路径配置 + this.usersDir = path.join(__dirname, '../../data/users'); + + // 内存存储配置 + this.memoryStorage = new Map(); // 存储图片Buffer数据 + this.linkMap = new Map(); // 图片链接映射 + this.metadataMap = new Map(); // 图片元数据映射 + this.userDataMap = new Map(); // 用户数据映射 + + // 内存管理配置 + this.maxMemoryUsage = 14 * 1024 * 1024 * 1024; // 14GB 最大内存使用量 + this.currentMemoryUsage = 0; + this.cleanupThreshold = 0.8; // 80%时开始清理 + + // 缓存策略配置 + this.maxImageAge = 24 * 60 * 60 * 1000; // 24小时过期 + this.maxImagesPerUser = 100; // 每用户最大图片数 + + this.initialized = false; + + // 定期清理内存 + this.startMemoryCleanup(); + } + + /** + * 初始化内存存储服务 + */ + async initialize() { + if (this.initialized) return; + + try { + // 初始化内存存储 + this.memoryStorage.clear(); + this.linkMap.clear(); + this.metadataMap.clear(); + this.userDataMap.clear(); + this.currentMemoryUsage = 0; + + this.initialized = true; + console.log('✅ HuggingfaceStorageService (Memory Mode) initialized successfully'); + console.log(`💾 Max memory usage: ${(this.maxMemoryUsage / 1024 / 1024 / 1024).toFixed(1)}GB`); + console.log(`🧹 Cleanup threshold: ${(this.cleanupThreshold * 100)}%`); + console.log(`⏰ Image expiry: ${this.maxImageAge / 1000 / 60 / 60}h`); + } catch (error) { + console.error('❌ Failed to initialize HuggingfaceStorageService:', error); + throw error; + } + } + + /** + * 启动内存清理定时器 + */ + startMemoryCleanup() { + // 每5分钟检查一次内存使用情况 + setInterval(() => { + this.performMemoryCleanup(); + }, 5 * 60 * 1000); + + console.log('🧹 Memory cleanup scheduler started (every 5 minutes)'); + } + + /** + * 执行内存清理 + */ + performMemoryCleanup() { + const memoryUsageRatio = this.currentMemoryUsage / this.maxMemoryUsage; + + if (memoryUsageRatio > this.cleanupThreshold) { + console.log(`🧹 Memory cleanup triggered (${(memoryUsageRatio * 100).toFixed(1)}% usage)`); + + // 清理过期图片 + this.cleanupExpiredImages(); + + // 如果内存使用仍然过高,清理最旧的图片 + if (this.currentMemoryUsage / this.maxMemoryUsage > this.cleanupThreshold) { + this.cleanupOldestImages(); + } + } + } + + /** + * 清理过期图片 + */ + cleanupExpiredImages() { + const now = Date.now(); + let cleanedCount = 0; + let freedMemory = 0; + + for (const [imageId, metadata] of this.metadataMap.entries()) { + const imageAge = now - new Date(metadata.createdAt).getTime(); + + if (imageAge > this.maxImageAge) { + const imageData = this.memoryStorage.get(imageId); + if (imageData) { + freedMemory += imageData.length; + this.memoryStorage.delete(imageId); + this.metadataMap.delete(imageId); + this.linkMap.delete(imageId); + cleanedCount++; + } + } + } + + this.currentMemoryUsage -= freedMemory; + + if (cleanedCount > 0) { + console.log(`🧹 Cleaned ${cleanedCount} expired images, freed ${(freedMemory / 1024 / 1024).toFixed(1)}MB`); + } + } + + /** + * 清理最旧的图片 + */ + cleanupOldestImages() { + const images = Array.from(this.metadataMap.entries()) + .sort((a, b) => new Date(a[1].createdAt) - new Date(b[1].createdAt)); + + const targetCleanup = Math.floor(images.length * 0.2); // 清理20%最旧的图片 + let cleanedCount = 0; + let freedMemory = 0; + + for (let i = 0; i < targetCleanup && i < images.length; i++) { + const [imageId, metadata] = images[i]; + const imageData = this.memoryStorage.get(imageId); + + if (imageData) { + freedMemory += imageData.length; + this.memoryStorage.delete(imageId); + this.metadataMap.delete(imageId); + this.linkMap.delete(imageId); + cleanedCount++; + } + } + + this.currentMemoryUsage -= freedMemory; + + if (cleanedCount > 0) { + console.log(`🧹 Cleaned ${cleanedCount} oldest images, freed ${(freedMemory / 1024 / 1024).toFixed(1)}MB`); + } + } + + + + /** + * 生成图片ID + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {number} pageIndex - 页面索引 + * @param {string} version - 版本号 + * @returns {string} 图片ID + */ + generateImageId(userId, pptId, pageIndex, version = 'latest') { + const data = `${userId}-${pptId}-${pageIndex}-${version}`; + return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16); + } + + /** + * 生成版本号 + * @returns {string} 版本号 + */ + generateVersion() { + return Date.now().toString(); + } + + /** + * 存储图片到内存 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {number} pageIndex - 页面索引 + * @param {Buffer} imageBuffer - 图片数据 + * @param {Object} options - 存储选项 + * @returns {Promise} 存储结果 + */ + async storeImage(userId, pptId, pageIndex, imageBuffer, options = {}) { + if (!this.initialized) { + await this.initialize(); + } + + const { + format = 'png', + quality = 0.9, + updateExisting = true + } = options; + + try { + // 检查内存使用情况 + if (this.currentMemoryUsage + imageBuffer.length > this.maxMemoryUsage) { + console.log('⚠️ Memory limit approaching, performing cleanup...'); + this.performMemoryCleanup(); + + // 如果清理后仍然超出限制,拒绝存储 + if (this.currentMemoryUsage + imageBuffer.length > this.maxMemoryUsage) { + throw new Error('Memory limit exceeded, cannot store image'); + } + } + + // 检查用户图片数量限制 + const userImages = Array.from(this.metadataMap.values()) + .filter(meta => meta.userId === userId); + + if (userImages.length >= this.maxImagesPerUser) { + // 删除用户最旧的图片 + const oldestImage = userImages + .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))[0]; + + if (oldestImage) { + this.deleteImageFromMemory(oldestImage.imageId); + } + } + + // 生成版本号 + const version = this.generateVersion(); + + // 生成图片ID + const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest'); + const versionedImageId = this.generateImageId(userId, pptId, pageIndex, version); + + // 如果已存在相同的图片ID,先删除旧的 + if (this.memoryStorage.has(imageId)) { + this.deleteImageFromMemory(imageId); + } + + // 存储图片到内存 + this.memoryStorage.set(imageId, imageBuffer); + this.memoryStorage.set(versionedImageId, imageBuffer); + + // 更新内存使用量 + this.currentMemoryUsage += imageBuffer.length * 2; // 存储了两份(latest和versioned) + + // 创建元数据 + const metadata = { + imageId, + versionedImageId, + userId, + pptId, + pageIndex, + version, + format, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + size: imageBuffer.length, + memoryStored: true + }; + + // 存储元数据 + this.metadataMap.set(imageId, metadata); + this.metadataMap.set(versionedImageId, { ...metadata, imageId: versionedImageId }); + + // 更新链接映射 + this.linkMap.set(imageId, metadata); + this.linkMap.set(versionedImageId, { ...metadata, imageId: versionedImageId }); + + // 更新用户数据统计 + if (!this.userDataMap.has(userId)) { + this.userDataMap.set(userId, { imageCount: 0, totalSize: 0 }); + } + const userData = this.userDataMap.get(userId); + userData.imageCount++; + userData.totalSize += imageBuffer.length; + + console.log(`✅ Image stored in memory: ${imageId} (${imageBuffer.length} bytes)`); + console.log(`💾 Memory usage: ${(this.currentMemoryUsage / 1024 / 1024).toFixed(1)}MB / ${(this.maxMemoryUsage / 1024 / 1024 / 1024).toFixed(1)}GB`); + + return { + success: true, + imageId, + versionedImageId, + version, + url: `/api/images/${imageId}`, + versionedUrl: `/api/images/${versionedImageId}`, + memoryStored: true, + size: imageBuffer.length, + memoryUsage: { + current: this.currentMemoryUsage, + max: this.maxMemoryUsage, + percentage: (this.currentMemoryUsage / this.maxMemoryUsage * 100).toFixed(1) + } + }; + } catch (error) { + console.error('❌ Failed to store image in memory:', error); + throw error; + } + } + + /** + * 从内存中删除图片 + * @param {string} imageId - 图片ID + */ + deleteImageFromMemory(imageId) { + const imageData = this.memoryStorage.get(imageId); + if (imageData) { + this.currentMemoryUsage -= imageData.length; + this.memoryStorage.delete(imageId); + this.metadataMap.delete(imageId); + this.linkMap.delete(imageId); + + console.log(`🗑️ Deleted image from memory: ${imageId} (freed ${imageData.length} bytes)`); + } + } + + /** + * 从内存中获取图片 + * @param {string} imageId - 图片ID + * @returns {Promise} 图片数据和元信息 + */ + async getImage(imageId) { + if (!this.initialized) { + await this.initialize(); + } + + // 从内存中获取图片数据 + const imageBuffer = this.memoryStorage.get(imageId); + if (!imageBuffer) { + throw new Error(`Image not found in memory: ${imageId}`); + } + + // 获取元数据 + const metadata = this.metadataMap.get(imageId); + if (!metadata) { + throw new Error(`Image metadata not found: ${imageId}`); + } + + try { + return { + success: true, + data: imageBuffer, + metadata: { + imageId, + format: metadata.format, + size: metadata.size, + createdAt: metadata.createdAt, + updatedAt: metadata.updatedAt, + version: metadata.version, + memoryStored: true + } + }; + } catch (error) { + console.error(`❌ Failed to get image ${imageId}:`, error); + throw error; + } + } + + /** + * 从内存中删除图片 + * @param {string} imageId - 图片ID + * @returns {Promise} 删除结果 + */ + async deleteImage(imageId) { + if (!this.initialized) { + await this.initialize(); + } + + const metadata = this.metadataMap.get(imageId); + if (!metadata) { + return false; + } + + try { + // 从内存中删除图片数据 + this.deleteImageFromMemory(imageId); + + // 如果有版本化的图片ID,也删除它 + if (metadata.versionedImageId && metadata.versionedImageId !== imageId) { + this.deleteImageFromMemory(metadata.versionedImageId); + } + + // 更新用户数据统计 + const userData = this.userDataMap.get(metadata.userId); + if (userData) { + userData.imageCount = Math.max(0, userData.imageCount - 1); + userData.totalSize = Math.max(0, userData.totalSize - metadata.size); + } + + console.log(`✅ Image deleted from memory: ${imageId}`); + return true; + } catch (error) { + console.error(`❌ Failed to delete image from memory ${imageId}:`, error); + return false; + } + } + + /** + * 批量存储PPT所有页面图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {Array} imageBuffers - 图片Buffer数组 + * @param {Object} options - 存储选项 + * @returns {Promise} 存储结果数组 + */ + async storeAllImages(userId, pptId, imageBuffers, options = {}) { + const results = []; + + for (let i = 0; i < imageBuffers.length; i++) { + try { + if (imageBuffers[i].success) { + const result = await this.storeImage( + userId, + pptId, + i, + imageBuffers[i].data, + options + ); + results.push({ + pageIndex: i, + success: true, + ...result + }); + } else { + results.push({ + pageIndex: i, + success: false, + error: imageBuffers[i].error + }); + } + } catch (error) { + results.push({ + pageIndex: i, + success: false, + error: error.message + }); + } + } + + return results; + } + + /** + * 存储PPT数据到Huggingface硬盘 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {Object} pptData - PPT数据 + * @returns {Promise} 存储结果 + */ + async storePPTData(userId, pptId, pptData) { + if (!this.initialized) { + await this.initialize(); + } + + try { + const userDir = path.join(this.usersDir, userId); + const pptDir = path.join(userDir, pptId); + + // 确保目录存在 + await fs.mkdir(pptDir, { recursive: true }); + + // 存储PPT数据 + const pptDataFile = path.join(pptDir, 'data.json'); + await fs.writeFile(pptDataFile, JSON.stringify(pptData, null, 2)); + + // 存储元数据 + const metadata = { + pptId, + userId, + title: pptData.title || '未命名演示文稿', + slidesCount: pptData.slides?.length || 0, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + storageType: 'huggingface', + size: JSON.stringify(pptData).length + }; + + const metaFile = path.join(pptDir, 'meta.json'); + await fs.writeFile(metaFile, JSON.stringify(metadata, null, 2)); + + console.log(`✅ PPT data stored to Huggingface: ${pptId} for user ${userId}`); + return { + success: true, + pptId, + metadata + }; + } catch (error) { + console.error(`❌ Failed to store PPT data ${pptId}:`, error); + throw error; + } + } + + /** + * 从Huggingface硬盘获取PPT数据 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @returns {Promise} PPT数据 + */ + async getPPTData(userId, pptId) { + if (!this.initialized) { + await this.initialize(); + } + + try { + const pptDataFile = path.join(this.usersDir, userId, pptId, 'data.json'); + const data = await fs.readFile(pptDataFile, 'utf-8'); + const pptData = JSON.parse(data); + + console.log(`✅ PPT data loaded from Huggingface: ${pptId} for user ${userId}`); + return pptData; + } catch (error) { + console.error(`❌ Failed to get PPT data ${pptId}:`, error); + throw error; + } + } + + /** + * 获取用户的PPT列表 + * @param {string} userId - 用户ID + * @returns {Promise} PPT列表 + */ + async getUserPPTList(userId) { + if (!this.initialized) { + await this.initialize(); + } + + try { + const userDir = path.join(this.usersDir, userId); + + // 检查用户目录是否存在 + try { + await fs.access(userDir); + } catch { + console.log(`📁 No PPTs found for user ${userId} in Huggingface storage`); + return []; + } + + const pptFolders = await fs.readdir(userDir, { withFileTypes: true }); + const pptList = []; + + for (const folder of pptFolders) { + if (folder.isDirectory()) { + try { + const metaFile = path.join(userDir, folder.name, 'meta.json'); + const metaData = await fs.readFile(metaFile, 'utf-8'); + const metadata = JSON.parse(metaData); + + pptList.push({ + name: folder.name, + title: metadata.title || '未命名演示文稿', + updatedAt: metadata.updatedAt || new Date().toISOString(), + slidesCount: metadata.slidesCount || 0, + storageType: 'huggingface', + size: metadata.size || 0, + repoUrl: 'Huggingface Storage' + }); + } catch (error) { + console.warn(`⚠️ Skipping invalid PPT folder ${folder.name}:`, error.message); + } + } + } + + console.log(`📁 Found ${pptList.length} PPTs in Huggingface storage for user ${userId}`); + return pptList.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + } catch (error) { + console.error(`❌ Failed to get PPT list for user ${userId}:`, error); + throw error; + } + } + + /** + * 删除PPT数据 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @returns {Promise} 删除结果 + */ + async deletePPTData(userId, pptId) { + if (!this.initialized) { + await this.initialize(); + } + + try { + const pptDir = path.join(this.usersDir, userId, pptId); + + // 递归删除PPT目录 + await fs.rm(pptDir, { recursive: true, force: true }); + + console.log(`✅ PPT data deleted from Huggingface: ${pptId} for user ${userId}`); + return true; + } catch (error) { + console.error(`❌ Failed to delete PPT data ${pptId}:`, error); + return false; + } + } + + /** + * 获取用户的所有图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID (可选) + * @returns {Promise} 图片列表 + */ + async getUserImages(userId, pptId = null) { + if (!this.initialized) { + await this.initialize(); + } + + const userImages = []; + + for (const [imageId, linkInfo] of this.linkMap.entries()) { + if (linkInfo.userId === userId) { + if (!pptId || linkInfo.pptId === pptId) { + userImages.push({ + imageId, + pptId: linkInfo.pptId, + pageIndex: linkInfo.pageIndex, + version: linkInfo.version, + format: linkInfo.format, + size: linkInfo.size, + url: `/api/images/${imageId}`, + createdAt: linkInfo.createdAt, + updatedAt: linkInfo.updatedAt + }); + } + } + } + + return userImages.sort((a, b) => { + if (a.pptId !== b.pptId) { + return a.pptId.localeCompare(b.pptId); + } + return a.pageIndex - b.pageIndex; + }); + } + + /** + * 清理过期图片 + * @param {number} maxAge - 最大保留时间(毫秒) + * @returns {Promise} 清理的图片数量 + */ + async cleanupExpiredImages(maxAge = 30 * 24 * 60 * 60 * 1000) { // 默认30天 + if (!this.initialized) { + await this.initialize(); + } + + const now = Date.now(); + let cleanedCount = 0; + + for (const [imageId, linkInfo] of this.linkMap.entries()) { + const createdAt = new Date(linkInfo.createdAt).getTime(); + if (now - createdAt > maxAge) { + await this.deleteImage(imageId); + cleanedCount++; + } + } + + console.log(`🧹 Cleaned up ${cleanedCount} expired images`); + return cleanedCount; + } + + + + /** + * 获取存储统计信息 + * @returns {Promise} 统计信息 + */ + async getStorageStats() { + if (!this.initialized) { + await this.initialize(); + } + + let totalSize = 0; + let totalImages = 0; + const userStats = {}; + + // 从内存存储中统计 + for (const [imageId, metadata] of this.metadataMap.entries()) { + totalSize += metadata.size || 0; + totalImages++; + + if (!userStats[metadata.userId]) { + userStats[metadata.userId] = { + imageCount: 0, + totalSize: 0, + ppts: new Set() + }; + } + + userStats[metadata.userId].imageCount++; + userStats[metadata.userId].totalSize += metadata.size || 0; + userStats[metadata.userId].ppts.add(metadata.pptId); + } + + // 转换Set为数组 + for (const userId in userStats) { + userStats[userId].pptCount = userStats[userId].ppts.size; + delete userStats[userId].ppts; + } + + return { + totalImages, + totalSize, + totalSizeMB: Math.round(totalSize / 1024 / 1024 * 100) / 100, + userCount: Object.keys(userStats).length, + userStats, + memoryUsage: { + current: this.currentMemoryUsage, + max: this.maxMemoryUsage, + currentMB: Math.round(this.currentMemoryUsage / 1024 / 1024 * 100) / 100, + maxGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100, + percentage: Math.round(this.currentMemoryUsage / this.maxMemoryUsage * 100 * 100) / 100, + imagesInMemory: this.memoryStorage.size + } + }; + } + + /** + * 获取内存使用情况 + * @returns {Object} 内存使用统计 + */ + getMemoryUsage() { + return { + current: this.currentMemoryUsage, + max: this.maxMemoryUsage, + currentMB: Math.round(this.currentMemoryUsage / 1024 / 1024 * 100) / 100, + maxGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100, + percentage: Math.round(this.currentMemoryUsage / this.maxMemoryUsage * 100 * 100) / 100, + imagesInMemory: this.memoryStorage.size, + metadataCount: this.metadataMap.size, + userCount: this.userDataMap.size + }; + } + + /** + * 清空所有内存数据 + */ + clearAllMemory() { + this.memoryStorage.clear(); + this.metadataMap.clear(); + this.linkMap.clear(); + this.userDataMap.clear(); + this.currentMemoryUsage = 0; + + console.log('🧹 All memory data cleared'); + } + + /** + * 健康检查 + */ + async healthCheck() { + try { + if (!this.initialized) { + return { + status: 'unhealthy', + message: 'Service not initialized', + details: { + initialized: false, + memoryUsage: 0, + imagesCount: 0 + } + }; + } + + // 检查内存使用情况 + const memoryUsage = this.getMemoryUsage(); + const isMemoryHealthy = memoryUsage.percentage < 90; // 90%以下认为健康 + + // 检查数据目录是否可访问 + let dirAccessible = true; + try { + await fs.access(this.usersDir); + } catch (error) { + dirAccessible = false; + } + + const isHealthy = isMemoryHealthy && dirAccessible; + + return { + status: isHealthy ? 'healthy' : 'unhealthy', + message: isHealthy + ? 'Huggingface Storage Service is running normally' + : 'Service has issues', + details: { + initialized: this.initialized, + memoryUsage: memoryUsage, + dirAccessible: dirAccessible, + usersDir: this.usersDir, + isMemoryHealthy: isMemoryHealthy, + maxMemoryUsageGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100, + cleanupThreshold: this.cleanupThreshold + } + }; + } catch (error) { + return { + status: 'unhealthy', + message: 'Health check failed', + details: { + error: error.message, + stack: error.stack + } + }; + } + } + + /** + * 获取用户的图片列表 + * @param {string} userId - 用户ID + * @returns {Array} 用户的图片列表 + */ + getUserImageList(userId) { + const userImages = []; + + for (const [imageId, metadata] of this.metadataMap.entries()) { + if (metadata.userId === userId) { + userImages.push({ + imageId, + pptId: metadata.pptId, + pageIndex: metadata.pageIndex, + format: metadata.format, + size: metadata.size, + createdAt: metadata.createdAt, + version: metadata.version + }); + } + } + + return userImages; + } + + /** + * 获取PPT的所有图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @returns {Array} PPT的图片列表 + */ + getPPTImages(userId, pptId) { + const pptImages = []; + + for (const [imageId, metadata] of this.metadataMap.entries()) { + if (metadata.userId === userId && metadata.pptId === pptId) { + pptImages.push({ + imageId, + pageIndex: metadata.pageIndex, + format: metadata.format, + size: metadata.size, + createdAt: metadata.createdAt, + version: metadata.version, + url: `/api/images/${imageId}` + }); + } + } + + return pptImages.sort((a, b) => a.pageIndex - b.pageIndex); + } + + /** + * 公开访问:根据用户ID、PPT ID和页面索引获取图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {number} pageIndex - 页面索引 + * @returns {Promise} 图片数据和元信息 + */ + async getPublicImage(userId, pptId, pageIndex) { + if (!this.initialized) { + await this.initialize(); + } + + // 生成图片ID + const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest'); + + // 从内存中获取图片数据 + const imageBuffer = this.memoryStorage.get(imageId); + if (!imageBuffer) { + throw new Error(`Public image not found: ${userId}/${pptId}/${pageIndex}`); + } + + // 获取元数据 + const metadata = this.metadataMap.get(imageId); + if (!metadata) { + throw new Error(`Public image metadata not found: ${userId}/${pptId}/${pageIndex}`); + } + + console.log(`✅ Public image accessed: ${userId}/${pptId}/${pageIndex} (${imageBuffer.length} bytes)`); + + return { + success: true, + data: imageBuffer, + metadata: { + imageId, + userId, + pptId, + pageIndex, + format: metadata.format, + size: metadata.size, + createdAt: metadata.createdAt, + updatedAt: metadata.updatedAt, + version: metadata.version, + memoryStored: true + } + }; + } + + /** + * 公开访问:检查图片是否存在 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {number} pageIndex - 页面索引 + * @returns {boolean} 图片是否存在 + */ + hasPublicImage(userId, pptId, pageIndex) { + if (!this.initialized) { + return false; + } + + const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest'); + return this.memoryStorage.has(imageId) && this.metadataMap.has(imageId); + } + + /** + * 公开访问:获取用户的PPT列表(仅包含有图片的PPT) + * @param {string} userId - 用户ID + * @returns {Array} PPT列表 + */ + getPublicPPTList(userId) { + if (!this.initialized) { + return []; + } + + const pptMap = new Map(); + + for (const [imageId, metadata] of this.metadataMap.entries()) { + if (metadata.userId === userId) { + if (!pptMap.has(metadata.pptId)) { + pptMap.set(metadata.pptId, { + pptId: metadata.pptId, + userId: metadata.userId, + pageCount: 0, + totalSize: 0, + createdAt: metadata.createdAt, + updatedAt: metadata.updatedAt, + pages: [] + }); + } + + const pptInfo = pptMap.get(metadata.pptId); + pptInfo.pageCount++; + pptInfo.totalSize += metadata.size; + pptInfo.pages.push({ + pageIndex: metadata.pageIndex, + format: metadata.format, + size: metadata.size, + url: `/api/public/image/${userId}/${metadata.pptId}/${metadata.pageIndex}` + }); + + // 更新最新时间 + if (new Date(metadata.updatedAt) > new Date(pptInfo.updatedAt)) { + pptInfo.updatedAt = metadata.updatedAt; + } + } + } + + // 排序页面并返回结果 + const result = Array.from(pptMap.values()); + result.forEach(ppt => { + ppt.pages.sort((a, b) => a.pageIndex - b.pageIndex); + }); + + return result.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + } +} + +// 创建单例实例 +const huggingfaceStorageService = new HuggingfaceStorageService(); + +export default huggingfaceStorageService; \ No newline at end of file diff --git a/backend/src/services/memoryStorageService.js b/backend/src/services/memoryStorageService.js new file mode 100644 index 0000000000000000000000000000000000000000..8cbd170d380645707c278bc0638de0d793bd3980 --- /dev/null +++ b/backend/src/services/memoryStorageService.js @@ -0,0 +1,69 @@ +import { v4 as uuidv4 } from 'uuid'; + +// 内存存储,用于开发和测试 +class MemoryStorageService { + constructor() { + this.storage = new Map(); // userId -> Map + } + + // 获取用户的所有PPT列表 + async getUserPPTList(userId) { + const userStorage = this.storage.get(userId) || new Map(); + const pptList = Array.from(userStorage.entries()).map(([pptId, pptData]) => ({ + name: pptId, + title: pptData.title || '未命名演示文稿', + lastModified: pptData.updatedAt, + repoIndex: 0, + repoUrl: 'memory://local' + })); + return pptList; + } + + // 获取PPT数据 + async getFile(userId, fileName) { + const pptId = fileName.replace('.json', ''); + const userStorage = this.storage.get(userId) || new Map(); + const pptData = userStorage.get(pptId); + + if (!pptData) { + return null; + } + + return { + content: pptData, + sha: 'memory-sha' + }; + } + + // 保存PPT数据 + async saveFile(userId, fileName, data) { + const pptId = fileName.replace('.json', ''); + + if (!this.storage.has(userId)) { + this.storage.set(userId, new Map()); + } + + const userStorage = this.storage.get(userId); + userStorage.set(pptId, { + ...data, + updatedAt: new Date().toISOString() + }); + + return { success: true }; + } + + // 删除PPT + async deleteFile(userId, fileName) { + const pptId = fileName.replace('.json', ''); + const userStorage = this.storage.get(userId); + + if (!userStorage || !userStorage.has(pptId)) { + throw new Error('File not found'); + } + + userStorage.delete(pptId); + return { success: true }; + } +} + +export default new MemoryStorageService(); \ No newline at end of file diff --git a/backend/src/services/persistentImageLinkService.js b/backend/src/services/persistentImageLinkService.js new file mode 100644 index 0000000000000000000000000000000000000000..53207b7c24bebb9b9a7270c4e773235b4d470c3a --- /dev/null +++ b/backend/src/services/persistentImageLinkService.js @@ -0,0 +1,1121 @@ +import express from 'express'; +import fs from 'fs/promises'; +import path from 'path'; +import crypto from 'crypto'; +import { fileURLToPath } from 'url'; +import huggingfaceStorageService from './huggingfaceStorageService.js'; +import backupSchedulerService from './backupSchedulerService.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * 持久化图片链接服务 + * 实现PPT页面图片的唯一链接管理、自动更新和定时备份 + */ +class PersistentImageLinkService { + constructor() { + this.initialized = false; + this.dataDir = process.env.HF_DATA_DIR || (process.platform === 'win32' ? './data' : '/data'); + this.linksDir = path.join(this.dataDir, 'persistent-links'); + this.linkMapFile = path.join(this.linksDir, 'link-map.json'); + + // 持久化链接映射: linkId -> { userId, pptId, pageIndex, linkId, createdAt, lastUpdated } + this.persistentLinks = new Map(); + + // PPT页面到链接的映射: "userId:pptId:pageIndex" -> linkId + this.pageToLinkMap = new Map(); + + // 图片更新队列 + this.updateQueue = new Set(); + this.isProcessingUpdates = false; + } + + /** + * 初始化服务 + */ + async initialize() { + if (this.initialized) return; + + try { + // 创建必要目录 + await fs.mkdir(this.linksDir, { recursive: true }); + + // 加载现有的链接映射 + await this.loadLinkMap(); + + // 启动定时备份任务(每8小时) + this.startBackupScheduler(); + + this.initialized = true; + console.log('✅ PersistentImageLinkService initialized successfully'); + console.log(`🔗 Persistent links directory: ${this.linksDir}`); + console.log(`📊 Loaded ${this.persistentLinks.size} persistent links`); + } catch (error) { + console.error('❌ Failed to initialize PersistentImageLinkService:', error); + throw error; + } + } + + /** + * 加载链接映射 + */ + async loadLinkMap() { + try { + const data = await fs.readFile(this.linkMapFile, 'utf8'); + const linkData = JSON.parse(data); + + this.persistentLinks.clear(); + this.pageToLinkMap.clear(); + + for (const [linkId, linkInfo] of Object.entries(linkData.persistentLinks || {})) { + this.persistentLinks.set(linkId, linkInfo); + const pageKey = `${linkInfo.userId}:${linkInfo.pptId}:${linkInfo.pageIndex}`; + this.pageToLinkMap.set(pageKey, linkId); + } + + console.log(`📋 Loaded ${this.persistentLinks.size} persistent links from storage`); + } catch (error) { + if (error.code !== 'ENOENT') { + console.warn('⚠️ Failed to load link map:', error.message); + } + // 文件不存在或损坏,从空开始 + this.persistentLinks.clear(); + this.pageToLinkMap.clear(); + } + } + + /** + * 保存链接映射 + */ + async saveLinkMap() { + try { + const linkData = { + version: '1.0', + lastUpdated: new Date().toISOString(), + persistentLinks: Object.fromEntries(this.persistentLinks) + }; + + await fs.writeFile(this.linkMapFile, JSON.stringify(linkData, null, 2)); + } catch (error) { + console.error('❌ Failed to save link map:', error); + } + } + + /** + * 生成唯一的持久化链接ID + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {string} slideId - 幻灯片唯一ID + * @returns {string} 持久化链接ID + */ + generatePersistentLinkId(userId, pptId, slideId) { + // 使用确定性哈希确保同一页面总是生成相同的链接ID + // 现在基于slideId而不是pageIndex,确保不受页面顺序影响 + const data = `persistent:${userId}:${pptId}:${slideId}`; + return crypto.createHash('sha256').update(data).digest('hex').substring(0, 20); + } + + /** + * 创建持久化链接(接收前端生成的图片数据) + * @param {string} userId - 用户ID + * @param {Object} linkData - 链接数据 + * @returns {Promise} 创建结果 + */ + async createLink(userId, linkData) { + if (!this.initialized) { + await this.initialize(); + } + + const { pptId, slideId, slideIndex, imageData, format = 'jpeg', metadata = {} } = linkData; + + // 优先使用slideId,如果没有则回退到slideIndex(向后兼容) + const uniqueId = slideId || slideIndex; + if (!uniqueId && uniqueId !== 0) { + throw new Error('Either slideId or slideIndex must be provided'); + } + + // 生成持久化链接ID + const linkId = this.generatePersistentLinkId(userId, pptId, uniqueId); + const pageKey = `${userId}:${pptId}:${uniqueId}`; + + try { + // 处理图片数据 + if (!imageData || !imageData.startsWith('data:image/')) { + throw new Error('Invalid image data format'); + } + + // 提取base64数据 + const base64Data = imageData.split(',')[1]; + const imageBuffer = Buffer.from(base64Data, 'base64'); + + // 保存图片到持久化目录 + const imagePath = path.join(this.linksDir, `${linkId}.${format}`); + await fs.writeFile(imagePath, imageBuffer); + + // 创建或更新链接信息 + const linkInfo = { + linkId, + userId, + pptId, + slideId: slideId || null, // 存储幻灯片唯一ID + pageIndex: slideIndex || null, // 保留页面索引用于向后兼容 + uniqueId, // 当前使用的唯一标识符 + createdAt: this.persistentLinks.get(linkId)?.createdAt || new Date().toISOString(), + lastUpdated: new Date().toISOString(), + updateCount: (this.persistentLinks.get(linkId)?.updateCount || 0) + 1, + hasImage: true, + imageSize: imageBuffer.length, + format, + imagePath, + metadata + }; + + this.persistentLinks.set(linkId, linkInfo); + this.pageToLinkMap.set(pageKey, linkId); + + // 保存映射 + await this.saveLinkMap(); + + // 调度备份 + this.scheduleBackup(userId); + + console.log(`✅ Created/updated persistent link: ${linkId} (${imageBuffer.length} bytes)`); + + // 构建完整的URL,确保包含正确的协议和端口 + const baseUrl = process.env.PUBLIC_URL ? + (process.env.PUBLIC_URL.startsWith('http') ? process.env.PUBLIC_URL : `http://${process.env.PUBLIC_URL}`) : + `http://localhost:${process.env.PORT || 7860}`; + + return { + success: true, + linkId, + imageUrl: `${baseUrl}/api/persistent-images/${linkId}`, + downloadUrl: `${baseUrl}/api/persistent-images/${linkId}?download=true`, + publicUrl: `${baseUrl}/api/persistent-images/${linkId}`, + metadata: linkInfo + }; + + } catch (error) { + console.error(`❌ Failed to create persistent link ${linkId}:`, error); + throw error; + } + } + + /** + * 获取或创建PPT页面的持久化链接 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {string|number} uniqueId - 幻灯片唯一ID或页面索引 + * @param {Object} slideData - 幻灯片数据(用于首次生成图片) + * @param {Object} options - 生成选项 + * @returns {Promise} 持久化链接信息 + */ + async getOrCreatePersistentLink(userId, pptId, uniqueId, slideData = null, options = {}) { + if (!this.initialized) { + await this.initialize(); + } + + const pageKey = `${userId}:${pptId}:${uniqueId}`; + let linkId = this.pageToLinkMap.get(pageKey); + + if (!linkId) { + // 创建新的持久化链接 + linkId = this.generatePersistentLinkId(userId, pptId, uniqueId); + + // 判断uniqueId是字符串(slideId)还是数字(pageIndex) + const isSlideId = typeof uniqueId === 'string'; + + const linkInfo = { + linkId, + userId, + pptId, + slideId: isSlideId ? uniqueId : null, + pageIndex: isSlideId ? null : uniqueId, + uniqueId, + createdAt: new Date().toISOString(), + lastUpdated: new Date().toISOString(), + updateCount: 0, + hasImage: false + }; + + this.persistentLinks.set(linkId, linkInfo); + this.pageToLinkMap.set(pageKey, linkId); + + // 保存映射 + await this.saveLinkMap(); + + console.log(`🔗 Created persistent link: ${linkId} for ${pageKey}`); + } + + // 如果提供了幻灯片数据,生成或更新图片 + if (slideData) { + await this.updatePageImage(linkId, slideData, options); + } + + const linkInfo = this.persistentLinks.get(linkId); + + // 构建完整的URL,确保包含正确的协议和端口 + const baseUrl = process.env.PUBLIC_URL ? + (process.env.PUBLIC_URL.startsWith('http') ? process.env.PUBLIC_URL : `http://${process.env.PUBLIC_URL}`) : + `http://localhost:${process.env.PORT || 7860}`; + + return { + success: true, + linkId, + url: `${baseUrl}/api/persistent-images/${linkId}`, + publicUrl: `${baseUrl}/api/persistent-images/${linkId}`, + ...linkInfo + }; + } + + /** + * 更新页面图片 + * @param {string} linkId - 持久化链接ID + * @param {Object} slideData - 幻灯片数据 + * @param {Object} options - 生成选项 + */ + async updatePageImage(linkId, slideData, options = {}) { + const linkInfo = this.persistentLinks.get(linkId); + if (!linkInfo) { + throw new Error(`Persistent link not found: ${linkId}`); + } + + try { + console.log(`🔄 Updating image for persistent link: ${linkId}`); + + // 生成新图片 + const generateOptions = { + format: 'jpg', + quality: 0.9, + width: 1920, + height: 1080, + viewportSize: 1000, + viewportRatio: 0.5625, + ...options + }; + + // 使用前端导出服务生成图片 + const uniqueId = linkInfo.uniqueId || linkInfo.slideId || linkInfo.pageIndex; + const imageBuffer = await this.generateImageWithFrontendExport( + linkInfo.userId, + linkInfo.pptId, + uniqueId, + slideData, + generateOptions + ); + + // 保存图片到持久化目录 + const fileExtension = generateOptions.format === 'jpg' ? 'jpg' : generateOptions.format; + const imagePath = path.join(this.linksDir, `${linkId}.${fileExtension}`); + await fs.writeFile(imagePath, imageBuffer); + + // 更新链接信息 + linkInfo.lastUpdated = new Date().toISOString(); + linkInfo.updateCount = (linkInfo.updateCount || 0) + 1; + linkInfo.hasImage = true; + linkInfo.imageSize = imageBuffer.length; + linkInfo.format = generateOptions.format; + linkInfo.imagePath = imagePath; + + this.persistentLinks.set(linkId, linkInfo); + + // 保存映射 + await this.saveLinkMap(); + + // 添加到备份队列 + this.scheduleBackup(linkInfo.userId); + + console.log(`✅ Updated image for persistent link: ${linkId} (${imageBuffer.length} bytes)`); + + } catch (error) { + console.error(`❌ Failed to update image for persistent link ${linkId}:`, error); + throw error; + } + } + + /** + * 直接更新持久化链接的图片内容 + * @param {string} linkId - 持久化链接ID + * @param {Buffer} imageBuffer - 图片数据Buffer + * @param {string} format - 图片格式 + * @returns {Promise} 更新结果 + */ + async updateImageContent(linkId, imageBuffer, format = 'jpeg') { + if (!this.initialized) { + await this.initialize(); + } + + const linkInfo = this.persistentLinks.get(linkId); + if (!linkInfo) { + return { + success: false, + error: `Persistent link not found: ${linkId}` + }; + } + + try { + console.log(`🔄 Updating image content for persistent link: ${linkId}`); + + // 保存图片到持久化目录 + const fileExtension = format === 'jpg' ? 'jpg' : format; + const imagePath = path.join(this.linksDir, `${linkId}.${fileExtension}`); + await fs.writeFile(imagePath, imageBuffer); + + // 更新链接信息 + linkInfo.lastUpdated = new Date().toISOString(); + linkInfo.updateCount = (linkInfo.updateCount || 0) + 1; + linkInfo.hasImage = true; + linkInfo.imageSize = imageBuffer.length; + linkInfo.format = format; + linkInfo.imagePath = imagePath; + + this.persistentLinks.set(linkId, linkInfo); + + // 保存映射 + await this.saveLinkMap(); + + // 添加到备份队列 + this.scheduleBackup(linkInfo.userId); + + console.log(`✅ Updated image content for persistent link: ${linkId} (${imageBuffer.length} bytes)`); + + return { + success: true, + linkId, + size: imageBuffer.length, + format, + lastUpdated: linkInfo.lastUpdated + }; + + } catch (error) { + console.error(`❌ Failed to update image content for persistent link ${linkId}:`, error); + return { + success: false, + error: error.message + }; + } + } + + /** + * 获取持久化链接的图片 + * @param {string} linkId - 持久化链接ID + * @returns {Promise} 图片数据和元信息 + */ + async getPersistentImage(linkId) { + if (!this.initialized) { + await this.initialize(); + } + + const linkInfo = this.persistentLinks.get(linkId); + if (!linkInfo) { + throw new Error(`Persistent link not found: ${linkId}`); + } + + // 如果没有图片信息,尝试查找可能存在的图片文件 + if (!linkInfo.hasImage || !linkInfo.imagePath) { + console.log(`⚠️ No image info for ${linkId}, attempting to find existing image files...`); + + // 尝试查找可能存在的图片文件 + const extensions = ['jpeg', 'jpg', 'png', 'webp']; + let foundImagePath = null; + let foundFormat = null; + + for (const ext of extensions) { + const potentialPath = path.join(this.linksDir, `${linkId}.${ext}`); + try { + await fs.access(potentialPath); + foundImagePath = path.relative(process.cwd(), potentialPath); + foundFormat = ext; + console.log(`✅ Found existing image: ${potentialPath}`); + break; + } catch (error) { + // 文件不存在,继续尝试下一个扩展名 + } + } + + if (!foundImagePath) { + throw new Error(`Image not available for persistent link: ${linkId}`); + } + + // 更新 linkInfo + linkInfo.hasImage = true; + linkInfo.imagePath = foundImagePath; + linkInfo.format = foundFormat; + this.persistentLinks.set(linkId, linkInfo); + await this.saveLinkMap(); + + console.log(`📝 Updated link info for ${linkId} with found image`); + } + + try { + // 确保使用绝对路径 + let imagePath = linkInfo.imagePath; + if (!path.isAbsolute(imagePath)) { + // 如果是相对路径,则相对于项目根目录 + imagePath = path.resolve(imagePath); + } + + console.log(`📖 Reading image from: ${imagePath}`); + + // 尝试读取文件,如果失败则尝试其他扩展名 + let imageBuffer; + try { + imageBuffer = await fs.readFile(imagePath); + } catch (readError) { + console.warn(`⚠️ Failed to read ${imagePath}, trying alternative extensions...`); + + // 尝试其他常见的图片扩展名 + const basePath = imagePath.replace(/\.[^.]+$/, ''); + const extensions = ['jpeg', 'jpg', 'png', 'webp']; + + for (const ext of extensions) { + const altPath = `${basePath}.${ext}`; + try { + console.log(`🔍 Trying: ${altPath}`); + imageBuffer = await fs.readFile(altPath); + console.log(`✅ Found image at: ${altPath}`); + + // 更新 linkInfo 中的路径信息 + linkInfo.imagePath = path.relative(process.cwd(), altPath); + linkInfo.format = ext; + this.persistentLinks.set(linkId, linkInfo); + await this.saveLinkMap(); + + break; + } catch (altError) { + // 继续尝试下一个扩展名 + } + } + + if (!imageBuffer) { + throw readError; // 如果所有尝试都失败,抛出原始错误 + } + } + + return { + success: true, + data: imageBuffer, + metadata: { + linkId, + format: linkInfo.format || 'png', + size: linkInfo.imageSize || imageBuffer.length, + createdAt: linkInfo.createdAt, + lastUpdated: linkInfo.lastUpdated, + updateCount: linkInfo.updateCount || 0 + } + }; + } catch (error) { + console.error(`❌ Failed to get persistent image ${linkId}:`, error); + console.error(`❌ Attempted to read from: ${linkInfo.imagePath}`); + throw error; + } + } + + /** + * 批量更新PPT所有页面的持久化链接 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {Array} slides - 幻灯片数据数组 + * @param {Object} options - 生成选项 + * @returns {Promise} 持久化链接信息数组 + */ + async updateAllPersistentLinks(userId, pptId, slides, options = {}) { + const results = []; + + for (let pageIndex = 0; pageIndex < slides.length; pageIndex++) { + try { + const slide = slides[pageIndex]; + // 优先使用slideId,如果不存在则使用pageIndex + const uniqueId = slide?.id || pageIndex; + + const result = await this.getOrCreatePersistentLink( + userId, + pptId, + uniqueId, + slide, + options + ); + + results.push({ + pageIndex, + slideId: slide?.id, + uniqueId, + success: true, + ...result + }); + } catch (error) { + console.error(`❌ Failed to update persistent link for page ${pageIndex}:`, error); + results.push({ + pageIndex, + slideId: slides[pageIndex]?.id, + success: false, + error: error.message + }); + } + } + + return results; + } + + /** + * 使用前端导出服务批量更新PPT所有页面的持久化链接 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {Array} slides - 幻灯片数据数组 + * @param {Object} options - 生成选项 + * @returns {Promise} 持久化链接信息数组 + */ + async updateAllPersistentLinksWithFrontendExport(userId, pptId, slides, options = {}) { + const results = []; + + console.log(`🔄 Starting batch update of ${slides.length} persistent links for PPT ${pptId}`); + + for (let pageIndex = 0; pageIndex < slides.length; pageIndex++) { + try { + const slide = slides[pageIndex]; + // 优先使用slideId,如果不存在则使用pageIndex + const uniqueId = slide?.id || pageIndex; + + // 获取或创建持久化链接(不生成图片) + const linkResult = await this.getOrCreatePersistentLink( + userId, + pptId, + uniqueId, + null, // 不传递slideData,避免立即生成图片 + options + ); + + // 使用前端导出服务生成图片 + const linkInfo = this.persistentLinks.get(linkResult.linkId); + if (linkInfo) { + try { + const imageBuffer = await this.generateImageWithFrontendExport( + userId, + pptId, + uniqueId, + slide, + options + ); + + // 保存图片 + const imagePath = path.join(this.linksDir, `${linkResult.linkId}.${options.format || 'jpg'}`); + await fs.writeFile(imagePath, imageBuffer); + + // 更新链接信息 + linkInfo.lastUpdated = new Date().toISOString(); + linkInfo.updateCount = (linkInfo.updateCount || 0) + 1; + linkInfo.hasImage = true; + linkInfo.imageSize = imageBuffer.length; + linkInfo.format = options.format || 'jpg'; + linkInfo.imagePath = imagePath; + + this.persistentLinks.set(linkResult.linkId, linkInfo); + + console.log(`✅ Updated persistent link ${linkResult.linkId} for page ${pageIndex} (${imageBuffer.length} bytes)`); + } catch (imageError) { + console.error(`❌ Failed to generate image for page ${pageIndex}:`, imageError); + throw imageError; + } + } + + results.push({ + pageIndex, + success: true, + linkId: linkResult.linkId, + url: linkResult.url, + publicUrl: linkResult.publicUrl + }); + + } catch (error) { + console.error(`❌ Failed to update persistent link for page ${pageIndex}:`, error); + results.push({ + pageIndex, + success: false, + error: error.message + }); + } + } + + // 保存映射 + await this.saveLinkMap(); + + // 添加到备份队列 + this.scheduleBackup(userId); + + const successCount = results.filter(r => r.success).length; + console.log(`✅ Batch update completed: ${successCount}/${slides.length} links updated successfully`); + + return results; + } + + /** + * 使用前端导出服务生成图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {string|number} uniqueId - 幻灯片唯一ID或页面索引 + * @param {Object} slideData - 幻灯片数据 + * @param {Object} options - 生成选项 + * @returns {Promise} 图片数据 + */ + async generateImageWithFrontendExport(userId, pptId, uniqueId, slideData, options = {}) { + try { + console.log(`🖼️ Generating JPG image for slide ${uniqueId} of PPT ${pptId}`); + + // 尝试使用Canvas生成JPG图片 + const { createCanvas } = await import('canvas'); + const width = Math.round(options.width || 1920); + const height = Math.round(options.height || 1080); + + const canvas = createCanvas(width, height); + const ctx = canvas.getContext('2d'); + + // 设置背景 + ctx.fillStyle = slideData?.background?.color || '#ffffff'; + ctx.fillRect(0, 0, width, height); + + // 添加边框 + ctx.strokeStyle = '#e0e0e0'; + ctx.lineWidth = 4; + ctx.strokeRect(0, 0, width, height); + + // 获取幻灯片信息 + const pageNumber = typeof uniqueId === 'number' ? uniqueId + 1 : '未知'; + const slideTitle = slideData?.title || `第 ${pageNumber} 页`; + const slideElements = slideData?.elements || []; + const elementCount = slideElements.length; + + // 绘制标题区域背景 + ctx.fillStyle = '#f8f9fa'; + ctx.fillRect(50, 50, width - 100, 120); + ctx.strokeStyle = '#dee2e6'; + ctx.lineWidth = 2; + ctx.strokeRect(50, 50, width - 100, 120); + + // 绘制标题文字 + ctx.fillStyle = '#d14424'; + ctx.font = 'bold 48px Arial, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(slideTitle, width / 2, 120); + + // 绘制内容信息 + ctx.fillStyle = '#6c757d'; + ctx.font = '32px Arial, sans-serif'; + ctx.fillText(`包含 ${elementCount} 个元素`, width / 2, height / 2); + + // 绘制页面信息 + ctx.fillStyle = '#adb5bd'; + ctx.font = '24px Arial, sans-serif'; + ctx.fillText(`PPT ID: ${pptId} | 页面: ${pageNumber}`, width / 2, height / 2 + 60); + + // 绘制时间戳 + ctx.fillStyle = '#ced4da'; + ctx.font = '20px Arial, sans-serif'; + ctx.fillText(`生成时间: ${new Date().toLocaleString('zh-CN')}`, width / 2, height - 50); + + // 绘制装饰性圆圈 + ctx.fillStyle = '#e9ecef'; + ctx.beginPath(); + ctx.arc(150, height - 150, 50, 0, 2 * Math.PI); + ctx.fill(); + ctx.strokeStyle = '#d14424'; + ctx.lineWidth = 4; + ctx.stroke(); + + // 绘制页码(修复变量名) + ctx.fillStyle = '#d14424'; + ctx.font = 'bold 28px Arial, sans-serif'; + ctx.textAlign = 'center'; + const displayPageNumber = typeof uniqueId === 'number' ? uniqueId + 1 : uniqueId; + ctx.fillText(displayPageNumber.toString(), 150, height - 140); + + // 转换为JPG Buffer + return canvas.toBuffer('image/jpeg', { quality: options.quality || 0.9 }); + + } catch (error) { + console.warn(`⚠️ Canvas module not available for slide ${uniqueId}, generating SVG placeholder:`, error.message); + + // 回退:生成一个SVG占位图片 + const width = Math.round(options.width || 1920); + const height = Math.round(options.height || 1080); + const pageNumber = typeof uniqueId === 'number' ? uniqueId + 1 : '未知'; + const slideTitle = slideData?.title || `第 ${pageNumber} 页`; + const slideElements = slideData?.elements || []; + const elementCount = slideElements.length; + + const svgContent = ` + + + + + + + + + ${this.escapeXml(slideTitle)} + + 包含 ${elementCount} 个元素 + + PPT ID: ${this.escapeXml(pptId)} | 页面: ${pageNumber} + + 生成时间: ${new Date().toLocaleString('zh-CN')} + + + + ${pageNumber} +`; + + return Buffer.from(svgContent, 'utf8'); + } + } + + /** + * 转义XML特殊字符 + * @param {string} text - 要转义的文本 + * @returns {string} 转义后的文本 + */ + escapeXml(text) { + if (!text) return ''; + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + /** + * 生成占位图片 + * @param {Object} options - 生成选项 + * @returns {Buffer} 图片数据 + */ + async generatePlaceholderImage(options = {}) { + try { + // 尝试使用 canvas 模块 + const { createCanvas } = await import('canvas'); + const width = Math.round(options.width || 1920); + const height = Math.round(options.height || 1080); + + const canvas = createCanvas(width, height); + const ctx = canvas.getContext('2d'); + + // 设置背景 + ctx.fillStyle = '#f5f5f5'; + ctx.fillRect(0, 0, width, height); + + // 绘制主要文字 + ctx.fillStyle = '#999999'; + ctx.font = '48px Arial, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('图片生成中...', width / 2, height / 2 - 30); + + // 绘制副标题 + ctx.fillStyle = '#cccccc'; + ctx.font = '28px Arial, sans-serif'; + ctx.fillText('Generating Image...', width / 2, height / 2 + 30); + + // 转换为JPG Buffer + return canvas.toBuffer('image/jpeg', { quality: options.quality || 0.9 }); + } catch (error) { + console.warn('⚠️ Canvas module not available, generating simple placeholder:', error.message); + // 如果Canvas失败,生成一个简单的SVG图片 + const width = Math.round(options.width || 1920); + const height = Math.round(options.height || 1080); + + const svgContent = ` + + + 图片生成中... + Generating Image... +`; + + return Buffer.from(svgContent, 'utf8'); + } + } + + /** + * 获取用户的所有持久化链接 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID(可选) + * @returns {Array} 持久化链接列表 + */ + getUserPersistentLinks(userId, pptId = null) { + const links = []; + + for (const [linkId, linkInfo] of this.persistentLinks) { + if (linkInfo.userId === userId && (!pptId || linkInfo.pptId === pptId)) { + // 使用正确的链接格式:/api/persistent-images/slideID + const baseUrl = process.env.PUBLIC_URL || 'http://localhost:7860'; + links.push({ + linkId, + url: `/api/persistent-images/${linkId}`, + publicUrl: `${baseUrl}/api/persistent-images/${linkId}`, + ...linkInfo + }); + } + } + + return links; + } + + /** + * 获取用户的所有持久化链接(别名方法) + * @param {string} userId - 用户ID + * @returns {Array} 持久化链接列表 + */ + getUserLinks(userId) { + return this.getUserPersistentLinks(userId); + } + + /** + * 通过 PPT ID 和 Slide ID 查找链接信息 + * @param {string} pptId - PPT ID + * @param {string} slideId - 幻灯片 ID + * @returns {Object|null} 链接信息或 null + */ + async findLinkByPptAndSlide(pptId, slideId) { + if (!this.initialized) { + await this.initialize(); + } + + // 遍历所有链接,查找匹配的 pptId 和 slideId + for (const [linkId, linkInfo] of this.persistentLinks) { + if (linkInfo.pptId === pptId && linkInfo.slideId === slideId) { + return linkInfo; + } + } + + // 如果没有找到基于 slideId 的匹配,尝试基于 uniqueId 的匹配(向后兼容) + for (const [linkId, linkInfo] of this.persistentLinks) { + if (linkInfo.pptId === pptId && linkInfo.uniqueId === slideId) { + return linkInfo; + } + } + + return null; + } + + /** + * 删除持久化链接 + * @param {string} linkId - 持久化链接ID + * @returns {Promise} 删除结果 + */ + async deletePersistentLink(linkId) { + const linkInfo = this.persistentLinks.get(linkId); + if (!linkInfo) { + return false; + } + + try { + // 删除图片文件 + if (linkInfo.imagePath) { + await fs.unlink(linkInfo.imagePath).catch(() => {}); + } + + // 从映射中移除 + const pageKey = `${linkInfo.userId}:${linkInfo.pptId}:${linkInfo.pageIndex}`; + this.persistentLinks.delete(linkId); + this.pageToLinkMap.delete(pageKey); + + // 保存映射 + await this.saveLinkMap(); + + console.log(`✅ Deleted persistent link: ${linkId}`); + return true; + } catch (error) { + console.error(`❌ Failed to delete persistent link ${linkId}:`, error); + return false; + } + } + + /** + * 调度备份 + * @param {string} userId - 用户ID + */ + scheduleBackup(userId) { + try { + backupSchedulerService.scheduleUserBackup(userId); + } catch (error) { + console.warn('⚠️ Failed to schedule backup:', error.message); + } + } + + /** + * 启动定时备份调度器(每8小时) + */ + startBackupScheduler() { + const BACKUP_INTERVAL = 8 * 60 * 60 * 1000; // 8小时 + + setInterval(async () => { + try { + console.log('🔄 Starting scheduled backup of persistent image links...'); + + // 获取所有用户ID + const userIds = new Set(); + for (const linkInfo of this.persistentLinks.values()) { + userIds.add(linkInfo.userId); + } + + // 为每个用户调度备份 + for (const userId of userIds) { + this.scheduleBackup(userId); + } + + console.log(`✅ Scheduled backup for ${userIds.size} users`); + } catch (error) { + console.error('❌ Failed to run scheduled backup:', error); + } + }, BACKUP_INTERVAL); + + console.log(`⏰ Backup scheduler started (every 8 hours)`); + } + + /** + * 根据用户ID、PPT ID和页面索引获取持久化图片 + * @param {string} userId - 用户ID + * @param {string} pptId - PPT ID + * @param {number} pageIndex - 页面索引 + * @returns {Object} 图片数据和元数据 + */ + async getPersistentImageByPage(userId, pptId, pageIndex) { + if (!this.initialized) { + await this.initialize(); + } + + const pageKey = `${userId}:${pptId}:${pageIndex}`; + const linkId = this.pageToLinkMap.get(pageKey); + + if (!linkId) { + throw new Error(`No persistent link found for page ${pageIndex} of PPT ${pptId}`); + } + + const linkInfo = this.persistentLinks.get(linkId); + if (!linkInfo || !linkInfo.hasImage) { + throw new Error(`No image available for link ${linkId}`); + } + + // 读取图片文件 + const imagePath = path.join(this.linksDir, `${linkId}.${linkInfo.format}`); + + try { + const imageBuffer = await fs.readFile(imagePath); + + return { + success: true, + data: imageBuffer, + metadata: { + linkId, + format: linkInfo.format, + size: linkInfo.imageSize, + createdAt: linkInfo.createdAt, + lastUpdated: linkInfo.lastUpdated + } + }; + } catch (error) { + throw new Error(`Failed to read image file: ${error.message}`); + } + } + + /** + * 获取服务统计信息 + * @returns {Object} 统计信息 + */ + getStats() { + const userStats = new Map(); + const pptStats = new Map(); + + for (const linkInfo of this.persistentLinks.values()) { + // 用户统计 + const userCount = userStats.get(linkInfo.userId) || 0; + userStats.set(linkInfo.userId, userCount + 1); + + // PPT统计 + const pptKey = `${linkInfo.userId}:${linkInfo.pptId}`; + const pptCount = pptStats.get(pptKey) || 0; + pptStats.set(pptKey, pptCount + 1); + } + + return { + totalLinks: this.persistentLinks.size, + totalUsers: userStats.size, + totalPPTs: pptStats.size, + linksWithImages: Array.from(this.persistentLinks.values()).filter(link => link.hasImage).length, + userStats: Object.fromEntries(userStats), + pptStats: Object.fromEntries(pptStats), + averageLinksPerUser: userStats.size > 0 ? this.persistentLinks.size / userStats.size : 0 + }; + } + + /** + * 健康检查 + */ + async healthCheck() { + try { + if (!this.initialized) { + return { + status: 'unhealthy', + message: 'Service not initialized', + details: { + initialized: false, + linksCount: 0 + } + }; + } + + // 检查数据目录是否可访问 + try { + await fs.access(this.linksDir); + } catch (error) { + return { + status: 'unhealthy', + message: 'Links directory not accessible', + details: { + linksDir: this.linksDir, + error: error.message + } + }; + } + + // 检查链接映射文件是否可读写 + try { + await fs.access(this.linkMapFile, fs.constants.R_OK | fs.constants.W_OK); + } catch (error) { + // 文件不存在是正常的,但如果存在却无法读写则有问题 + if (error.code !== 'ENOENT') { + return { + status: 'unhealthy', + message: 'Link map file not accessible', + details: { + linkMapFile: this.linkMapFile, + error: error.message + } + }; + } + } + + return { + status: 'healthy', + message: 'Persistent Image Link Service is running normally', + details: { + initialized: this.initialized, + linksCount: this.persistentLinks.size, + linksDir: this.linksDir, + linkMapFile: this.linkMapFile, + updateQueueSize: this.updateQueue.size, + isProcessingUpdates: this.isProcessingUpdates + } + }; + } catch (error) { + return { + status: 'unhealthy', + message: 'Health check failed', + details: { + error: error.message, + stack: error.stack + } + }; + } + } +} + +export default new PersistentImageLinkService(); \ No newline at end of file diff --git a/backend/src/utils/htmlGenerator.js b/backend/src/utils/htmlGenerator.js new file mode 100644 index 0000000000000000000000000000000000000000..8e1be60c1ec0a90393022b57ab6f2141c5c14c20 --- /dev/null +++ b/backend/src/utils/htmlGenerator.js @@ -0,0 +1,419 @@ +/** + * HTML Generator Utility + * 用于生成幻灯片的 HTML 内容 + */ + +/** + * 生成幻灯片的 HTML 内容 + * @param {Object} slideData - 幻灯片数据 + * @param {number} slideIndex - 幻灯片索引 + * @param {Object} options - 渲染选项 + * @returns {string} HTML 字符串 + */ +export function generateSlideHTML(slideData, slideIndex = 0, options = {}) { + const { + width = 1000, + height = 562, + backgroundColor = '#ffffff', + scale = 1 + } = options; + + if (!slideData || !slideData.slides || !slideData.slides[slideIndex]) { + throw new Error(`Invalid slide data or slide index: ${slideIndex}`); + } + + const slide = slideData.slides[slideIndex]; + const slideBackground = slide.background || backgroundColor; + + // 生成元素的 HTML + const elementsHTML = generateElementsHTML(slide.elements || [], { width, height, scale }); + + return ` + + + + + + Slide ${slideIndex + 1} + + + +
+ ${elementsHTML} +
+ + + `.trim(); +} + +/** + * 生成元素的 HTML + * @param {Array} elements - 元素数组 + * @param {Object} options - 渲染选项 + * @returns {string} 元素 HTML 字符串 + */ +function generateElementsHTML(elements, options = {}) { + if (!Array.isArray(elements)) { + return ''; + } + + return elements.map(element => { + try { + return generateElementHTML(element, options); + } catch (error) { + console.warn(`Failed to generate HTML for element:`, element, error); + return ''; + } + }).join('\n'); +} + +/** + * 生成单个元素的 HTML + * @param {Object} element - 元素数据 + * @param {Object} options - 渲染选项 + * @returns {string} 元素 HTML 字符串 + */ +function generateElementHTML(element, options = {}) { + if (!element || typeof element !== 'object') { + return ''; + } + + const { + type, + left = 0, + top = 0, + width = 100, + height = 100, + rotate = 0, + opacity = 1 + } = element; + + const baseStyle = ` + left: ${left}px; + top: ${top}px; + width: ${width}px; + height: ${height}px; + transform: rotate(${rotate}deg); + opacity: ${opacity}; + `; + + switch (type) { + case 'text': + return generateTextElementHTML(element, baseStyle); + case 'image': + return generateImageElementHTML(element, baseStyle); + case 'shape': + return generateShapeElementHTML(element, baseStyle); + case 'line': + return generateLineElementHTML(element, baseStyle); + case 'chart': + return generateChartElementHTML(element, baseStyle); + case 'table': + return generateTableElementHTML(element, baseStyle); + default: + console.warn(`Unknown element type: ${type}`); + return ''; + } +} + +/** + * 生成文本元素 HTML + */ +function generateTextElementHTML(element, baseStyle) { + const { + content = '', + fontSize = 14, + fontFamily = 'Arial', + color = '#000000', + fontWeight = 'normal', + fontStyle = 'normal', + textDecoration = 'none', + textAlign = 'left', + lineHeight = 1.2 + } = element; + + const textStyle = ` + font-size: ${fontSize}px; + font-family: ${fontFamily}; + color: ${color}; + font-weight: ${fontWeight}; + font-style: ${fontStyle}; + text-decoration: ${textDecoration}; + text-align: ${textAlign}; + line-height: ${lineHeight}; + `; + + return ` +
+ ${escapeHtml(content)} +
+ `; +} + +/** + * 生成图片元素 HTML + */ +function generateImageElementHTML(element, baseStyle) { + const { src = '', alt = '' } = element; + + if (!src) { + return ` +
+
+ 图片加载失败 +
+
+ `; + } + + return ` +
+ ${escapeHtml(alt)} +
+ `; +} + +/** + * 生成形状元素 HTML + */ +function generateShapeElementHTML(element, baseStyle) { + const { + fill = '#ffffff', + stroke = '#000000', + strokeWidth = 1, + borderRadius = 0 + } = element; + + const shapeStyle = ` + background: ${fill}; + border: ${strokeWidth}px solid ${stroke}; + border-radius: ${borderRadius}px; + `; + + return ` +
+
+ `; +} + +/** + * 生成线条元素 HTML + */ +function generateLineElementHTML(element, baseStyle) { + const { + stroke = '#000000', + strokeWidth = 2 + } = element; + + const lineStyle = ` + background: ${stroke}; + height: ${strokeWidth}px; + color: ${stroke}; + `; + + return ` +
+
+ `; +} + +/** + * 生成图表元素 HTML + */ +function generateChartElementHTML(element, baseStyle) { + // 简化的图表渲染,实际项目中可能需要更复杂的图表库 + return ` +
+
+ 📊 图表 +
+
+ `; +} + +/** + * 生成表格元素 HTML + */ +function generateTableElementHTML(element, baseStyle) { + const { data = [] } = element; + + if (!Array.isArray(data) || data.length === 0) { + return ` +
+
+ 📋 空表格 +
+
+ `; + } + + const tableHTML = data.map(row => { + if (!Array.isArray(row)) return ''; + const cellsHTML = row.map(cell => `${escapeHtml(String(cell))}`).join(''); + return `${cellsHTML}`; + }).join(''); + + return ` +
+ + ${tableHTML} +
+
+ `; +} + +/** + * HTML 转义函数 + * @param {string} text - 需要转义的文本 + * @returns {string} 转义后的文本 + */ +function escapeHtml(text) { + if (typeof text !== 'string') { + return String(text); + } + + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + return text.replace(/[&<>"']/g, m => map[m]); +} + +/** + * 生成完整的 PPT HTML(包含所有幻灯片) + * @param {Object} pptData - PPT 数据 + * @param {Object} options - 渲染选项 + * @returns {string} 完整的 HTML 字符串 + */ +export function generatePPTHTML(pptData, options = {}) { + if (!pptData || !pptData.slides || !Array.isArray(pptData.slides)) { + throw new Error('Invalid PPT data'); + } + + const { width = 1000, height = 562 } = options; + + const slidesHTML = pptData.slides.map((slide, index) => { + try { + return generateSlideHTML(pptData, index, options); + } catch (error) { + console.warn(`Failed to generate HTML for slide ${index}:`, error); + return ` +
+ 幻灯片 ${index + 1} 渲染失败 +
+ `; + } + }).join('\n\n'); + + return ` + + + + + + PPT Preview + + + +
+ ${slidesHTML} +
+ + + `.trim(); +} + +export default { + generateSlideHTML, + generatePPTHTML +}; \ No newline at end of file diff --git a/backend/src/utils/logger.js b/backend/src/utils/logger.js new file mode 100644 index 0000000000000000000000000000000000000000..5b366a41b345c07eb9ab2c5e2ba4569f597fb250 --- /dev/null +++ b/backend/src/utils/logger.js @@ -0,0 +1,103 @@ +import winston from 'winston'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 确保日志目录存在 +const logDir = path.join(__dirname, '../../logs'); +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); +} + +// 自定义日志格式 +const logFormat = winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.errors({ stack: true }), + winston.format.printf(({ level, message, timestamp, stack }) => { + if (stack) { + return `${timestamp} [${level.toUpperCase()}]: ${message}\n${stack}`; + } + return `${timestamp} [${level.toUpperCase()}]: ${message}`; + }) +); + +// 创建logger实例 +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: logFormat, + transports: [ + // 控制台输出 + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + logFormat + ) + }), + + // 错误日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'error.log'), + level: 'error', + maxsize: 5242880, // 5MB + maxFiles: 5 + }), + + // 综合日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'combined.log'), + maxsize: 5242880, // 5MB + maxFiles: 5 + }) + ], + + // 处理未捕获的异常 + exceptionHandlers: [ + new winston.transports.File({ + filename: path.join(logDir, 'exceptions.log') + }) + ], + + // 处理未处理的Promise拒绝 + rejectionHandlers: [ + new winston.transports.File({ + filename: path.join(logDir, 'rejections.log') + }) + ] +}); + +// 在生产环境中不输出到控制台 +if (process.env.NODE_ENV === 'production') { + logger.remove(logger.transports[0]); // 移除控制台传输 +} + +// 添加请求日志中间件 +export const requestLogger = (req, res, next) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + const logData = { + method: req.method, + url: req.url, + status: res.statusCode, + duration: `${duration}ms`, + ip: req.ip || req.connection.remoteAddress, + userAgent: req.get('User-Agent') + }; + + if (res.statusCode >= 400) { + logger.warn(`HTTP ${res.statusCode} - ${JSON.stringify(logData)}`); + } else { + logger.info(`HTTP ${res.statusCode} - ${JSON.stringify(logData)}`); + } + }); + + next(); +}; + +export default logger; \ No newline at end of file diff --git a/backend/test-frontend-export.js b/backend/test-frontend-export.js new file mode 100644 index 0000000000000000000000000000000000000000..b71390baa7e6aff6aad1dc4b487b0d2eb524a9a7 --- /dev/null +++ b/backend/test-frontend-export.js @@ -0,0 +1,90 @@ +import fetch from 'node-fetch'; + +/** + * 测试前端导出功能复刻API + */ +async function testFrontendExportReplication() { + const baseUrl = 'http://localhost:3001'; + + // 测试用的认证token(需要替换为实际的token) + const authToken = 'your-auth-token-here'; + + // 测试数据 + const testData = { + pptId: 'test-ppt-id', + slideIndex: 0, + format: 'jpeg', + quality: 0.9, + width: 1600, + height: 900, + ignoreWebfont: true, + backgroundColor: '#ffffff', + pixelRatio: 1 + }; + + console.log('🧪 Testing Frontend Export Replication API...'); + + try { + // 测试单个幻灯片导出 + console.log('\n1. Testing single slide export...'); + const singleResponse = await fetch(`${baseUrl}/api/export/frontend-replicate-single`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }, + body: JSON.stringify(testData) + }); + + if (singleResponse.ok) { + const singleResult = await singleResponse.json(); + console.log('✅ Single export success:', { + linkId: singleResult.linkId, + imageUrl: singleResult.imageUrl, + downloadUrl: singleResult.downloadUrl + }); + } else { + const error = await singleResponse.text(); + console.log('❌ Single export failed:', singleResponse.status, error); + } + + // 测试批量导出 + console.log('\n2. Testing batch export...'); + const batchData = { + ...testData, + rangeType: 'all' + }; + + const batchResponse = await fetch(`${baseUrl}/api/export/frontend-replicate-batch`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }, + body: JSON.stringify(batchData) + }); + + if (batchResponse.ok) { + const batchResult = await batchResponse.json(); + console.log('✅ Batch export success:', { + totalSlides: batchResult.totalSlides, + successCount: batchResult.successCount, + errorCount: batchResult.errorCount, + resultsCount: batchResult.results?.length || 0 + }); + } else { + const error = await batchResponse.text(); + console.log('❌ Batch export failed:', batchResponse.status, error); + } + + } catch (error) { + console.error('🚨 Test failed with error:', error.message); + } +} + +// 运行测试 +if (import.meta.url === `file://${process.argv[1]}`) { + testFrontendExportReplication(); +} + +export { testFrontendExportReplication }; \ No newline at end of file diff --git a/backend/test-simple-fix.js b/backend/test-simple-fix.js new file mode 100644 index 0000000000000000000000000000000000000000..eeddd8c131f101de3a52d213a03006ac45392610 --- /dev/null +++ b/backend/test-simple-fix.js @@ -0,0 +1,55 @@ +import huggingfaceStorageService from './src/services/huggingfaceStorageService.js'; + +// 简单测试Huggingface环境修复 +async function testSimpleFix() { + console.log('🧪 Testing Huggingface storage fix...'); + + try { + // 测试存储服务初始化 + console.log('\n📁 Testing storage service initialization...'); + await huggingfaceStorageService.initialize(); + console.log('✅ Storage service initialized successfully'); + console.log(`📁 Data directory: ${huggingfaceStorageService.dataDir}`); + + // 测试目录创建 + console.log('\n📂 Testing directory creation...'); + const testUserId = 'PS02'; + const testPptId = 'test-ppt-' + Date.now(); + + try { + await huggingfaceStorageService.storePPTData(testUserId, testPptId, { + title: 'Test PPT for Huggingface', + slides: [{ + id: 'slide1', + elements: [{ + type: 'text', + content: 'Hello Huggingface!' + }] + }] + }); + console.log('✅ PPT data storage test passed - no permission errors!'); + + // 测试数据读取 + const storedData = await huggingfaceStorageService.getPPTData(testUserId, testPptId); + if (storedData && storedData.title === 'Test PPT for Huggingface') { + console.log('✅ PPT data retrieval test passed!'); + } else { + console.log('⚠️ PPT data retrieval test failed'); + } + + } catch (storageError) { + console.error('❌ PPT data storage test failed:', storageError.message); + if (storageError.code === 'EACCES') { + console.error('🚨 Permission denied error - the fix may not be working in production'); + } + } + + console.log('\n🎉 Core storage tests completed!'); + + } catch (error) { + console.error('❌ Test failed:', error.message); + } +} + +// 运行测试 +testSimpleFix().catch(console.error); \ No newline at end of file